1 // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY
  2 (function(win) {
  3 	var whiteSpaceRe = /^\s*|\s*$/g,
  4 		undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
  5 
  6 	var tinymce = {
  7 		majorVersion : '3',
  8 
  9 		minorVersion : '5.4.1',
 10 
 11 		releaseDate : '2012-06-24',
 12 
 13 		_init : function() {
 14 			var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
 15 
 16 			t.isOpera = win.opera && opera.buildNumber;
 17 
 18 			t.isWebKit = /WebKit/.test(ua);
 19 
 20 			t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
 21 
 22 			t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
 23 
 24 			t.isIE7 = t.isIE && /MSIE [7]/.test(ua);
 25 
 26 			t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
 27 
 28 			t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
 29 
 30 			t.isGecko = !t.isWebKit && /Gecko/.test(ua);
 31 
 32 			t.isMac = ua.indexOf('Mac') != -1;
 33 
 34 			t.isAir = /adobeair/i.test(ua);
 35 
 36 			t.isIDevice = /(iPad|iPhone)/.test(ua);
 37 			
 38 			t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
 39 
 40 			// TinyMCE .NET webcontrol might be setting the values for TinyMCE
 41 			if (win.tinyMCEPreInit) {
 42 				t.suffix = tinyMCEPreInit.suffix;
 43 				t.baseURL = tinyMCEPreInit.base;
 44 				t.query = tinyMCEPreInit.query;
 45 				return;
 46 			}
 47 
 48 			// Get suffix and base
 49 			t.suffix = '';
 50 
 51 			// If base element found, add that infront of baseURL
 52 			nl = d.getElementsByTagName('base');
 53 			for (i=0; i<nl.length; i++) {
 54 				v = nl[i].href;
 55 				if (v) {
 56 					// Host only value like http://site.com or http://site.com:8008
 57 					if (/^https?:\/\/[^\/]+$/.test(v))
 58 						v += '/';
 59 
 60 					base = v ? v.match(/.*\//)[0] : ''; // Get only directory
 61 				}
 62 			}
 63 
 64 			function getBase(n) {
 65 				if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
 66 					if (/_(src|dev)\.js/g.test(n.src))
 67 						t.suffix = '_src';
 68 
 69 					if ((p = n.src.indexOf('?')) != -1)
 70 						t.query = n.src.substring(p + 1);
 71 
 72 					t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
 73 
 74 					// If path to script is relative and a base href was found add that one infront
 75 					// the src property will always be an absolute one on non IE browsers and IE 8
 76 					// so this logic will basically only be executed on older IE versions
 77 					if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
 78 						t.baseURL = base + t.baseURL;
 79 
 80 					return t.baseURL;
 81 				}
 82 
 83 				return null;
 84 			};
 85 
 86 			// Check document
 87 			nl = d.getElementsByTagName('script');
 88 			for (i=0; i<nl.length; i++) {
 89 				if (getBase(nl[i]))
 90 					return;
 91 			}
 92 
 93 			// Check head
 94 			n = d.getElementsByTagName('head')[0];
 95 			if (n) {
 96 				nl = n.getElementsByTagName('script');
 97 				for (i=0; i<nl.length; i++) {
 98 					if (getBase(nl[i]))
 99 						return;
100 				}
101 			}
102 
103 			return;
104 		},
105 
106 		is : function(o, t) {
107 			if (!t)
108 				return o !== undef;
109 
110 			if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
111 				return true;
112 
113 			return typeof(o) == t;
114 		},
115 
116 		makeMap : function(items, delim, map) {
117 			var i;
118 
119 			items = items || [];
120 			delim = delim || ',';
121 
122 			if (typeof(items) == "string")
123 				items = items.split(delim);
124 
125 			map = map || {};
126 
127 			i = items.length;
128 			while (i--)
129 				map[items[i]] = {};
130 
131 			return map;
132 		},
133 
134 		each : function(o, cb, s) {
135 			var n, l;
136 
137 			if (!o)
138 				return 0;
139 
140 			s = s || o;
141 
142 			if (o.length !== undef) {
143 				// Indexed arrays, needed for Safari
144 				for (n=0, l = o.length; n < l; n++) {
145 					if (cb.call(s, o[n], n, o) === false)
146 						return 0;
147 				}
148 			} else {
149 				// Hashtables
150 				for (n in o) {
151 					if (o.hasOwnProperty(n)) {
152 						if (cb.call(s, o[n], n, o) === false)
153 							return 0;
154 					}
155 				}
156 			}
157 
158 			return 1;
159 		},
160 
161 
162 		map : function(a, f) {
163 			var o = [];
164 
165 			tinymce.each(a, function(v) {
166 				o.push(f(v));
167 			});
168 
169 			return o;
170 		},
171 
172 		grep : function(a, f) {
173 			var o = [];
174 
175 			tinymce.each(a, function(v) {
176 				if (!f || f(v))
177 					o.push(v);
178 			});
179 
180 			return o;
181 		},
182 
183 		inArray : function(a, v) {
184 			var i, l;
185 
186 			if (a) {
187 				for (i = 0, l = a.length; i < l; i++) {
188 					if (a[i] === v)
189 						return i;
190 				}
191 			}
192 
193 			return -1;
194 		},
195 
196 		extend : function(obj, ext) {
197 			var i, l, name, args = arguments, value;
198 
199 			for (i = 1, l = args.length; i < l; i++) {
200 				ext = args[i];
201 				for (name in ext) {
202 					if (ext.hasOwnProperty(name)) {
203 						value = ext[name];
204 
205 						if (value !== undef) {
206 							obj[name] = value;
207 						}
208 					}
209 				}
210 			}
211 
212 			return obj;
213 		},
214 
215 
216 		trim : function(s) {
217 			return (s ? '' + s : '').replace(whiteSpaceRe, '');
218 		},
219 
220 		create : function(s, p, root) {
221 			var t = this, sp, ns, cn, scn, c, de = 0;
222 
223 			// Parse : <prefix> <class>:<super class>
224 			s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
225 			cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
226 
227 			// Create namespace for new class
228 			ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
229 
230 			// Class already exists
231 			if (ns[cn])
232 				return;
233 
234 			// Make pure static class
235 			if (s[2] == 'static') {
236 				ns[cn] = p;
237 
238 				if (this.onCreate)
239 					this.onCreate(s[2], s[3], ns[cn]);
240 
241 				return;
242 			}
243 
244 			// Create default constructor
245 			if (!p[cn]) {
246 				p[cn] = function() {};
247 				de = 1;
248 			}
249 
250 			// Add constructor and methods
251 			ns[cn] = p[cn];
252 			t.extend(ns[cn].prototype, p);
253 
254 			// Extend
255 			if (s[5]) {
256 				sp = t.resolve(s[5]).prototype;
257 				scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
258 
259 				// Extend constructor
260 				c = ns[cn];
261 				if (de) {
262 					// Add passthrough constructor
263 					ns[cn] = function() {
264 						return sp[scn].apply(this, arguments);
265 					};
266 				} else {
267 					// Add inherit constructor
268 					ns[cn] = function() {
269 						this.parent = sp[scn];
270 						return c.apply(this, arguments);
271 					};
272 				}
273 				ns[cn].prototype[cn] = ns[cn];
274 
275 				// Add super methods
276 				t.each(sp, function(f, n) {
277 					ns[cn].prototype[n] = sp[n];
278 				});
279 
280 				// Add overridden methods
281 				t.each(p, function(f, n) {
282 					// Extend methods if needed
283 					if (sp[n]) {
284 						ns[cn].prototype[n] = function() {
285 							this.parent = sp[n];
286 							return f.apply(this, arguments);
287 						};
288 					} else {
289 						if (n != cn)
290 							ns[cn].prototype[n] = f;
291 					}
292 				});
293 			}
294 
295 			// Add static methods
296 			t.each(p['static'], function(f, n) {
297 				ns[cn][n] = f;
298 			});
299 
300 			if (this.onCreate)
301 				this.onCreate(s[2], s[3], ns[cn].prototype);
302 		},
303 
304 		walk : function(o, f, n, s) {
305 			s = s || this;
306 
307 			if (o) {
308 				if (n)
309 					o = o[n];
310 
311 				tinymce.each(o, function(o, i) {
312 					if (f.call(s, o, i, n) === false)
313 						return false;
314 
315 					tinymce.walk(o, f, n, s);
316 				});
317 			}
318 		},
319 
320 		createNS : function(n, o) {
321 			var i, v;
322 
323 			o = o || win;
324 
325 			n = n.split('.');
326 			for (i=0; i<n.length; i++) {
327 				v = n[i];
328 
329 				if (!o[v])
330 					o[v] = {};
331 
332 				o = o[v];
333 			}
334 
335 			return o;
336 		},
337 
338 		resolve : function(n, o) {
339 			var i, l;
340 
341 			o = o || win;
342 
343 			n = n.split('.');
344 			for (i = 0, l = n.length; i < l; i++) {
345 				o = o[n[i]];
346 
347 				if (!o)
348 					break;
349 			}
350 
351 			return o;
352 		},
353 
354 		addUnload : function(f, s) {
355 			var t = this, unload;
356 
357 			unload = function() {
358 				var li = t.unloads, o, n;
359 
360 				if (li) {
361 					// Call unload handlers
362 					for (n in li) {
363 						o = li[n];
364 
365 						if (o && o.func)
366 							o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
367 					}
368 
369 					// Detach unload function
370 					if (win.detachEvent) {
371 						win.detachEvent('onbeforeunload', fakeUnload);
372 						win.detachEvent('onunload', unload);
373 					} else if (win.removeEventListener)
374 						win.removeEventListener('unload', unload, false);
375 
376 					// Destroy references
377 					t.unloads = o = li = w = unload = 0;
378 
379 					// Run garbarge collector on IE
380 					if (win.CollectGarbage)
381 						CollectGarbage();
382 				}
383 			};
384 
385 			function fakeUnload() {
386 				var d = document;
387 
388 				function stop() {
389 					// Prevent memory leak
390 					d.detachEvent('onstop', stop);
391 
392 					// Call unload handler
393 					if (unload)
394 						unload();
395 
396 					d = 0;
397 				};
398 
399 				// Is there things still loading, then do some magic
400 				if (d.readyState == 'interactive') {
401 					// Fire unload when the currently loading page is stopped
402 					if (d)
403 						d.attachEvent('onstop', stop);
404 
405 					// Remove onstop listener after a while to prevent the unload function
406 					// to execute if the user presses cancel in an onbeforeunload
407 					// confirm dialog and then presses the browser stop button
408 					win.setTimeout(function() {
409 						if (d)
410 							d.detachEvent('onstop', stop);
411 					}, 0);
412 				}
413 			};
414 
415 			f = {func : f, scope : s || this};
416 
417 			if (!t.unloads) {
418 				// Attach unload handler
419 				if (win.attachEvent) {
420 					win.attachEvent('onunload', unload);
421 					win.attachEvent('onbeforeunload', fakeUnload);
422 				} else if (win.addEventListener)
423 					win.addEventListener('unload', unload, false);
424 
425 				// Setup initial unload handler array
426 				t.unloads = [f];
427 			} else
428 				t.unloads.push(f);
429 
430 			return f;
431 		},
432 
433 		removeUnload : function(f) {
434 			var u = this.unloads, r = null;
435 
436 			tinymce.each(u, function(o, i) {
437 				if (o && o.func == f) {
438 					u.splice(i, 1);
439 					r = f;
440 					return false;
441 				}
442 			});
443 
444 			return r;
445 		},
446 
447 		explode : function(s, d) {
448 			if (!s || tinymce.is(s, 'array')) {
449 				return s;
450 			}
451 
452 			return tinymce.map(s.split(d || ','), tinymce.trim);
453 		},
454 
455 		_addVer : function(u) {
456 			var v;
457 
458 			if (!this.query)
459 				return u;
460 
461 			v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
462 
463 			if (u.indexOf('#') == -1)
464 				return u + v;
465 
466 			return u.replace('#', v + '#');
467 		},
468 
469 		// Fix function for IE 9 where regexps isn't working correctly
470 		// Todo: remove me once MS fixes the bug
471 		_replace : function(find, replace, str) {
472 			// On IE9 we have to fake $x replacement
473 			if (isRegExpBroken) {
474 				return str.replace(find, function() {
475 					var val = replace, args = arguments, i;
476 
477 					for (i = 0; i < args.length - 2; i++) {
478 						if (args[i] === undef) {
479 							val = val.replace(new RegExp('\\$' + i, 'g'), '');
480 						} else {
481 							val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
482 						}
483 					}
484 
485 					return val;
486 				});
487 			}
488 
489 			return str.replace(find, replace);
490 		}
491 
492 		};
493 
494 	// Initialize the API
495 	tinymce._init();
496 
497 	// Expose tinymce namespace to the global namespace (window)
498 	win.tinymce = win.tinyMCE = tinymce;
499 
500 	// Describe the different namespaces
501 
502 	})(window);
503 
504 
505 
506 tinymce.create('tinymce.util.Dispatcher', {
507 	scope : null,
508 	listeners : null,
509 	inDispatch: false,
510 
511 	Dispatcher : function(scope) {
512 		this.scope = scope || this;
513 		this.listeners = [];
514 	},
515 
516 	add : function(callback, scope) {
517 		this.listeners.push({cb : callback, scope : scope || this.scope});
518 
519 		return callback;
520 	},
521 
522 	addToTop : function(callback, scope) {
523 		var self = this, listener = {cb : callback, scope : scope || self.scope};
524 
525 		// Create new listeners if addToTop is executed in a dispatch loop
526 		if (self.inDispatch) {
527 			self.listeners = [listener].concat(self.listeners);
528 		} else {
529 			self.listeners.unshift(listener);
530 		}
531 
532 		return callback;
533 	},
534 
535 	remove : function(callback) {
536 		var listeners = this.listeners, output = null;
537 
538 		tinymce.each(listeners, function(listener, i) {
539 			if (callback == listener.cb) {
540 				output = listener;
541 				listeners.splice(i, 1);
542 				return false;
543 			}
544 		});
545 
546 		return output;
547 	},
548 
549 	dispatch : function() {
550 		var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;
551 
552 		self.inDispatch = true;
553 		
554 		// Needs to be a real loop since the listener count might change while looping
555 		// And this is also more efficient
556 		for (i = 0; i < listeners.length; i++) {
557 			listener = listeners[i];
558 			returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);
559 
560 			if (returnValue === false)
561 				break;
562 		}
563 
564 		self.inDispatch = false;
565 
566 		return returnValue;
567 	}
568 
569 	});
570 
571 (function() {
572 	var each = tinymce.each;
573 
574 	tinymce.create('tinymce.util.URI', {
575 		URI : function(u, s) {
576 			var t = this, o, a, b, base_url;
577 
578 			// Trim whitespace
579 			u = tinymce.trim(u);
580 
581 			// Default settings
582 			s = t.settings = s || {};
583 
584 			// Strange app protocol that isn't http/https or local anchor
585 			// For example: mailto,skype,tel etc.
586 			if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
587 				t.source = u;
588 				return;
589 			}
590 
591 			// Absolute path with no host, fake host and protocol
592 			if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
593 				u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
594 
595 			// Relative path http:// or protocol relative //path
596 			if (!/^[\w\-]*:?\/\//.test(u)) {
597 				base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
598 				u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
599 			}
600 
601 			// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
602 			u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
603 			u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
604 			each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
605 				var s = u[i];
606 
607 				// Zope 3 workaround, they use @@something
608 				if (s)
609 					s = s.replace(/\(mce_at\)/g, '@@');
610 
611 				t[v] = s;
612 			});
613 
614 			b = s.base_uri;
615 			if (b) {
616 				if (!t.protocol)
617 					t.protocol = b.protocol;
618 
619 				if (!t.userInfo)
620 					t.userInfo = b.userInfo;
621 
622 				if (!t.port && t.host === 'mce_host')
623 					t.port = b.port;
624 
625 				if (!t.host || t.host === 'mce_host')
626 					t.host = b.host;
627 
628 				t.source = '';
629 			}
630 
631 			//t.path = t.path || '/';
632 		},
633 
634 		setPath : function(p) {
635 			var t = this;
636 
637 			p = /^(.*?)\/?(\w+)?$/.exec(p);
638 
639 			// Update path parts
640 			t.path = p[0];
641 			t.directory = p[1];
642 			t.file = p[2];
643 
644 			// Rebuild source
645 			t.source = '';
646 			t.getURI();
647 		},
648 
649 		toRelative : function(u) {
650 			var t = this, o;
651 
652 			if (u === "./")
653 				return u;
654 
655 			u = new tinymce.util.URI(u, {base_uri : t});
656 
657 			// Not on same domain/port or protocol
658 			if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
659 				return u.getURI();
660 
661 			var tu = t.getURI(), uu = u.getURI();
662 			
663 			// Allow usage of the base_uri when relative_urls = true
664 			if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu))
665 				return tu;
666 
667 			o = t.toRelPath(t.path, u.path);
668 
669 			// Add query
670 			if (u.query)
671 				o += '?' + u.query;
672 
673 			// Add anchor
674 			if (u.anchor)
675 				o += '#' + u.anchor;
676 
677 			return o;
678 		},
679 	
680 		toAbsolute : function(u, nh) {
681 			u = new tinymce.util.URI(u, {base_uri : this});
682 
683 			return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
684 		},
685 
686 		toRelPath : function(base, path) {
687 			var items, bp = 0, out = '', i, l;
688 
689 			// Split the paths
690 			base = base.substring(0, base.lastIndexOf('/'));
691 			base = base.split('/');
692 			items = path.split('/');
693 
694 			if (base.length >= items.length) {
695 				for (i = 0, l = base.length; i < l; i++) {
696 					if (i >= items.length || base[i] != items[i]) {
697 						bp = i + 1;
698 						break;
699 					}
700 				}
701 			}
702 
703 			if (base.length < items.length) {
704 				for (i = 0, l = items.length; i < l; i++) {
705 					if (i >= base.length || base[i] != items[i]) {
706 						bp = i + 1;
707 						break;
708 					}
709 				}
710 			}
711 
712 			if (bp === 1)
713 				return path;
714 
715 			for (i = 0, l = base.length - (bp - 1); i < l; i++)
716 				out += "../";
717 
718 			for (i = bp - 1, l = items.length; i < l; i++) {
719 				if (i != bp - 1)
720 					out += "/" + items[i];
721 				else
722 					out += items[i];
723 			}
724 
725 			return out;
726 		},
727 
728 		toAbsPath : function(base, path) {
729 			var i, nb = 0, o = [], tr, outPath;
730 
731 			// Split paths
732 			tr = /\/$/.test(path) ? '/' : '';
733 			base = base.split('/');
734 			path = path.split('/');
735 
736 			// Remove empty chunks
737 			each(base, function(k) {
738 				if (k)
739 					o.push(k);
740 			});
741 
742 			base = o;
743 
744 			// Merge relURLParts chunks
745 			for (i = path.length - 1, o = []; i >= 0; i--) {
746 				// Ignore empty or .
747 				if (path[i].length === 0 || path[i] === ".")
748 					continue;
749 
750 				// Is parent
751 				if (path[i] === '..') {
752 					nb++;
753 					continue;
754 				}
755 
756 				// Move up
757 				if (nb > 0) {
758 					nb--;
759 					continue;
760 				}
761 
762 				o.push(path[i]);
763 			}
764 
765 			i = base.length - nb;
766 
767 			// If /a/b/c or /
768 			if (i <= 0)
769 				outPath = o.reverse().join('/');
770 			else
771 				outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
772 
773 			// Add front / if it's needed
774 			if (outPath.indexOf('/') !== 0)
775 				outPath = '/' + outPath;
776 
777 			// Add traling / if it's needed
778 			if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
779 				outPath += tr;
780 
781 			return outPath;
782 		},
783 
784 		getURI : function(nh) {
785 			var s, t = this;
786 
787 			// Rebuild source
788 			if (!t.source || nh) {
789 				s = '';
790 
791 				if (!nh) {
792 					if (t.protocol)
793 						s += t.protocol + '://';
794 
795 					if (t.userInfo)
796 						s += t.userInfo + '@';
797 
798 					if (t.host)
799 						s += t.host;
800 
801 					if (t.port)
802 						s += ':' + t.port;
803 				}
804 
805 				if (t.path)
806 					s += t.path;
807 
808 				if (t.query)
809 					s += '?' + t.query;
810 
811 				if (t.anchor)
812 					s += '#' + t.anchor;
813 
814 				t.source = s;
815 			}
816 
817 			return t.source;
818 		}
819 	});
820 })();
821 
822 (function() {
823 	var each = tinymce.each;
824 
825 	tinymce.create('static tinymce.util.Cookie', {
826 		getHash : function(n) {
827 			var v = this.get(n), h;
828 
829 			if (v) {
830 				each(v.split('&'), function(v) {
831 					v = v.split('=');
832 					h = h || {};
833 					h[unescape(v[0])] = unescape(v[1]);
834 				});
835 			}
836 
837 			return h;
838 		},
839 
840 		setHash : function(n, v, e, p, d, s) {
841 			var o = '';
842 
843 			each(v, function(v, k) {
844 				o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
845 			});
846 
847 			this.set(n, o, e, p, d, s);
848 		},
849 
850 		get : function(n) {
851 			var c = document.cookie, e, p = n + "=", b;
852 
853 			// Strict mode
854 			if (!c)
855 				return;
856 
857 			b = c.indexOf("; " + p);
858 
859 			if (b == -1) {
860 				b = c.indexOf(p);
861 
862 				if (b !== 0)
863 					return null;
864 			} else
865 				b += 2;
866 
867 			e = c.indexOf(";", b);
868 
869 			if (e == -1)
870 				e = c.length;
871 
872 			return unescape(c.substring(b + p.length, e));
873 		},
874 
875 		set : function(n, v, e, p, d, s) {
876 			document.cookie = n + "=" + escape(v) +
877 				((e) ? "; expires=" + e.toGMTString() : "") +
878 				((p) ? "; path=" + escape(p) : "") +
879 				((d) ? "; domain=" + d : "") +
880 				((s) ? "; secure" : "");
881 		},
882 
883 		remove : function(name, path, domain) {
884 			var date = new Date();
885 
886 			date.setTime(date.getTime() - 1000);
887 
888 			this.set(name, '', date, path, domain);
889 		}
890 	});
891 })();
892 
893 (function() {
894 	function serialize(o, quote) {
895 		var i, v, t, name;
896 
897 		quote = quote || '"';
898 
899 		if (o == null)
900 			return 'null';
901 
902 		t = typeof o;
903 
904 		if (t == 'string') {
905 			v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
906 
907 			return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
908 				// Make sure single quotes never get encoded inside double quotes for JSON compatibility
909 				if (quote === '"' && a === "'")
910 					return a;
911 
912 				i = v.indexOf(b);
913 
914 				if (i + 1)
915 					return '\\' + v.charAt(i + 1);
916 
917 				a = b.charCodeAt().toString(16);
918 
919 				return '\\u' + '0000'.substring(a.length) + a;
920 			}) + quote;
921 		}
922 
923 		if (t == 'object') {
924 			if (o.hasOwnProperty && o instanceof Array) {
925 					for (i=0, v = '['; i<o.length; i++)
926 						v += (i > 0 ? ',' : '') + serialize(o[i], quote);
927 
928 					return v + ']';
929 				}
930 
931 				v = '{';
932 
933 				for (name in o) {
934 					if (o.hasOwnProperty(name)) {
935 						v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : '';
936 					}
937 				}
938 
939 				return v + '}';
940 		}
941 
942 		return '' + o;
943 	};
944 
945 	tinymce.util.JSON = {
946 		serialize: serialize,
947 
948 		parse: function(s) {
949 			try {
950 				return eval('(' + s + ')');
951 			} catch (ex) {
952 				// Ignore
953 			}
954 		}
955 
956 		};
957 })();
958 
959 tinymce.create('static tinymce.util.XHR', {
960 	send : function(o) {
961 		var x, t, w = window, c = 0;
962 
963 		function ready() {
964 			if (!o.async || x.readyState == 4 || c++ > 10000) {
965 				if (o.success && c < 10000 && x.status == 200)
966 					o.success.call(o.success_scope, '' + x.responseText, x, o);
967 				else if (o.error)
968 					o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
969 
970 				x = null;
971 			} else
972 				w.setTimeout(ready, 10);
973 		};
974 
975 		// Default settings
976 		o.scope = o.scope || this;
977 		o.success_scope = o.success_scope || o.scope;
978 		o.error_scope = o.error_scope || o.scope;
979 		o.async = o.async === false ? false : true;
980 		o.data = o.data || '';
981 
982 		function get(s) {
983 			x = 0;
984 
985 			try {
986 				x = new ActiveXObject(s);
987 			} catch (ex) {
988 			}
989 
990 			return x;
991 		};
992 
993 		x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
994 
995 		if (x) {
996 			if (x.overrideMimeType)
997 				x.overrideMimeType(o.content_type);
998 
999 			x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
1000 
1001 			if (o.content_type)
1002 				x.setRequestHeader('Content-Type', o.content_type);
1003 
1004 			x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
1005 
1006 			x.send(o.data);
1007 
1008 			// Syncronous request
1009 			if (!o.async)
1010 				return ready();
1011 
1012 			// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
1013 			t = w.setTimeout(ready, 10);
1014 		}
1015 	}
1016 });
1017 
1018 (function() {
1019 	var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
1020 
1021 	tinymce.create('tinymce.util.JSONRequest', {
1022 		JSONRequest : function(s) {
1023 			this.settings = extend({
1024 			}, s);
1025 			this.count = 0;
1026 		},
1027 
1028 		send : function(o) {
1029 			var ecb = o.error, scb = o.success;
1030 
1031 			o = extend(this.settings, o);
1032 
1033 			o.success = function(c, x) {
1034 				c = JSON.parse(c);
1035 
1036 				if (typeof(c) == 'undefined') {
1037 					c = {
1038 						error : 'JSON Parse error.'
1039 					};
1040 				}
1041 
1042 				if (c.error)
1043 					ecb.call(o.error_scope || o.scope, c.error, x);
1044 				else
1045 					scb.call(o.success_scope || o.scope, c.result);
1046 			};
1047 
1048 			o.error = function(ty, x) {
1049 				if (ecb)
1050 					ecb.call(o.error_scope || o.scope, ty, x);
1051 			};
1052 
1053 			o.data = JSON.serialize({
1054 				id : o.id || 'c' + (this.count++),
1055 				method : o.method,
1056 				params : o.params
1057 			});
1058 
1059 			// JSON content type for Ruby on rails. Bug: #1883287
1060 			o.content_type = 'application/json';
1061 
1062 			XHR.send(o);
1063 		},
1064 
1065 		'static' : {
1066 			sendRPC : function(o) {
1067 				return new tinymce.util.JSONRequest().send(o);
1068 			}
1069 		}
1070 	});
1071 }());
1072 (function(tinymce){
1073 	tinymce.VK = {
1074 		BACKSPACE: 8,
1075 		DELETE: 46,
1076 		DOWN: 40,
1077 		ENTER: 13,
1078 		LEFT: 37,
1079 		RIGHT: 39,
1080 		SPACEBAR: 32,
1081 		TAB: 9,
1082 		UP: 38,
1083 
1084 		modifierPressed: function (e) {
1085 			return e.shiftKey || e.ctrlKey || e.altKey;
1086 		},
1087 
1088 		metaKeyPressed: function(e) {
1089 			return tinymce.isMac ? e.metaKey : e.ctrlKey;
1090 		}
1091 	};
1092 })(tinymce);
1093 
1094 tinymce.util.Quirks = function(editor) {
1095 	var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings;
1096 
1097 	function setEditorCommandState(cmd, state) {
1098 		try {
1099 			editor.getDoc().execCommand(cmd, false, state);
1100 		} catch (ex) {
1101 			// Ignore
1102 		}
1103 	}
1104 
1105 	function getDocumentMode() {
1106 		var documentMode = editor.getDoc().documentMode;
1107 
1108 		return documentMode ? documentMode : 6;
1109 	};
1110 
1111 	function cleanupStylesWhenDeleting() {
1112 		function removeMergedFormatSpans(isDelete) {
1113 			var rng, blockElm, node, clonedSpan;
1114 
1115 			rng = selection.getRng();
1116 
1117 			// Find root block
1118 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1119 
1120 			// On delete clone the root span of the next block element
1121 			if (isDelete)
1122 				blockElm = dom.getNext(blockElm, dom.isBlock);
1123 
1124 			// Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace
1125 			if (blockElm) {
1126 				node = blockElm.firstChild;
1127 
1128 				// Ignore empty text nodes
1129 				while (node && node.nodeType == 3 && node.nodeValue.length === 0)
1130 					node = node.nextSibling;
1131 
1132 				if (node && node.nodeName === 'SPAN') {
1133 					clonedSpan = node.cloneNode(false);
1134 				}
1135 			}
1136 
1137 			// Do the backspace/delete action
1138 			editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1139 
1140 			// Find all odd apple-style-spans
1141 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1142 			tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {
1143 				var bm = selection.getBookmark();
1144 
1145 				if (clonedSpan) {
1146 					dom.replace(clonedSpan.cloneNode(false), span, true);
1147 				} else {
1148 					dom.remove(span, true);
1149 				}
1150 
1151 				// Restore the selection
1152 				selection.moveToBookmark(bm);
1153 			});
1154 		};
1155 
1156 		editor.onKeyDown.add(function(editor, e) {
1157 			var isDelete;
1158 
1159 			isDelete = e.keyCode == DELETE;
1160 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1161 				e.preventDefault();
1162 				removeMergedFormatSpans(isDelete);
1163 			}
1164 		});
1165 
1166 		editor.addCommand('Delete', function() {removeMergedFormatSpans();});
1167 	};
1168 	
1169 	function emptyEditorWhenDeleting() {
1170 		function serializeRng(rng) {
1171 			var body = dom.create("body");
1172 			var contents = rng.cloneContents();
1173 			body.appendChild(contents);
1174 			return selection.serializer.serialize(body, {format: 'html'});
1175 		}
1176 
1177 		function allContentsSelected(rng) {
1178 			var selection = serializeRng(rng);
1179 
1180 			var allRng = dom.createRng();
1181 			allRng.selectNode(editor.getBody());
1182 
1183 			var allSelection = serializeRng(allRng);//console.log(selection, "----", allSelection);
1184 			return selection === allSelection;
1185 		}
1186 
1187 		editor.onKeyDown.add(function(editor, e) {
1188 			var keyCode = e.keyCode, isCollapsed;
1189 
1190 			// Empty the editor if it's needed for example backspace at <p><b>|</b></p>
1191 			if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) {
1192 				isCollapsed = editor.selection.isCollapsed();
1193 
1194 				// Selection is collapsed but the editor isn't empty
1195 				if (isCollapsed && !dom.isEmpty(editor.getBody())) {
1196 					return;
1197 				}
1198 
1199 				// IE deletes all contents correctly when everything is selected
1200 				if (tinymce.isIE && !isCollapsed) {
1201 					return;
1202 				}
1203 
1204 				// Selection isn't collapsed but not all the contents is selected
1205 				if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
1206 					return;
1207 				}
1208 
1209 				// Manually empty the editor
1210 				editor.setContent('');
1211 				editor.selection.setCursorLocation(editor.getBody(), 0);
1212 				editor.nodeChanged();
1213 			}
1214 		});
1215 	};
1216 
1217 	function selectAll() {
1218 		editor.onKeyDown.add(function(editor, e) {
1219 			if (e.keyCode == 65 && VK.metaKeyPressed(e)) {
1220 				e.preventDefault();
1221 				editor.execCommand('SelectAll');
1222 			}
1223 		});
1224 	};
1225 
1226 	function inputMethodFocus() {
1227 		if (!editor.settings.content_editable) {
1228 			// Case 1 IME doesn't initialize if you focus the document
1229 			dom.bind(editor.getDoc(), 'focusin', function(e) {
1230 				selection.setRng(selection.getRng());
1231 			});
1232 
1233 			// Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
1234 			dom.bind(editor.getDoc(), 'mousedown', function(e) {
1235 				if (e.target == editor.getDoc().documentElement) {
1236 					editor.getWin().focus();
1237 					selection.setRng(selection.getRng());
1238 				}
1239 			});
1240 		}
1241 	};
1242 
1243 	function removeHrOnBackspace() {
1244 		editor.onKeyDown.add(function(editor, e) {
1245 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1246 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1247 					var node = selection.getNode();
1248 					var previousSibling = node.previousSibling;
1249 
1250 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
1251 						dom.remove(previousSibling);
1252 						tinymce.dom.Event.cancel(e);
1253 					}
1254 				}
1255 			}
1256 		})
1257 	}
1258 
1259 	function focusBody() {
1260 		// Fix for a focus bug in FF 3.x where the body element
1261 		// wouldn't get proper focus if the user clicked on the HTML element
1262 		if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
1263 			editor.onMouseDown.add(function(editor, e) {
1264 				if (e.target.nodeName === "HTML") {
1265 					var body = editor.getBody();
1266 
1267 					// Blur the body it's focused but not correctly focused
1268 					body.blur();
1269 
1270 					// Refocus the body after a little while
1271 					setTimeout(function() {
1272 						body.focus();
1273 					}, 0);
1274 				}
1275 			});
1276 		}
1277 	};
1278 
1279 	function selectControlElements() {
1280 		editor.onClick.add(function(editor, e) {
1281 			e = e.target;
1282 
1283 			// Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
1284 			// WebKit can't even do simple things like selecting an image
1285 			// Needs tobe the setBaseAndExtend or it will fail to select floated images
1286 			if (/^(IMG|HR)$/.test(e.nodeName)) {
1287 				selection.getSel().setBaseAndExtent(e, 0, e, 1);
1288 			}
1289 
1290 			if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) {
1291 				selection.select(e);
1292 			}
1293 
1294 			editor.nodeChanged();
1295 		});
1296 	};
1297 
1298 	function removeStylesWhenDeletingAccrossBlockElements() {
1299 		function getAttributeApplyFunction() {
1300 			var template = dom.getAttribs(selection.getStart().cloneNode(false));
1301 
1302 			return function() {
1303 				var target = selection.getStart();
1304 
1305 				if (target !== editor.getBody()) {
1306 					dom.setAttrib(target, "style", null);
1307 
1308 					tinymce.each(template, function(attr) {
1309 						target.setAttributeNode(attr.cloneNode(true));
1310 					});
1311 				}
1312 			};
1313 		}
1314 
1315 		function isSelectionAcrossElements() {
1316 			return !selection.isCollapsed() && selection.getStart() != selection.getEnd();
1317 		}
1318 
1319 		function blockEvent(editor, e) {
1320 			e.preventDefault();
1321 			return false;
1322 		}
1323 
1324 		editor.onKeyPress.add(function(editor, e) {
1325 			var applyAttributes;
1326 
1327 			if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
1328 				applyAttributes = getAttributeApplyFunction();
1329 				editor.getDoc().execCommand('delete', false, null);
1330 				applyAttributes();
1331 				e.preventDefault();
1332 				return false;
1333 			}
1334 		});
1335 
1336 		dom.bind(editor.getDoc(), 'cut', function(e) {
1337 			var applyAttributes;
1338 
1339 			if (isSelectionAcrossElements()) {
1340 				applyAttributes = getAttributeApplyFunction();
1341 				editor.onKeyUp.addToTop(blockEvent);
1342 
1343 				setTimeout(function() {
1344 					applyAttributes();
1345 					editor.onKeyUp.remove(blockEvent);
1346 				}, 0);
1347 			}
1348 		});
1349 	}
1350 
1351 	function selectionChangeNodeChanged() {
1352 		var lastRng, selectionTimer;
1353 
1354 		dom.bind(editor.getDoc(), 'selectionchange', function() {
1355 			if (selectionTimer) {
1356 				clearTimeout(selectionTimer);
1357 				selectionTimer = 0;
1358 			}
1359 
1360 			selectionTimer = window.setTimeout(function() {
1361 				var rng = selection.getRng();
1362 
1363 				// Compare the ranges to see if it was a real change or not
1364 				if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {
1365 					editor.nodeChanged();
1366 					lastRng = rng;
1367 				}
1368 			}, 50);
1369 		});
1370 	}
1371 
1372 	function ensureBodyHasRoleApplication() {
1373 		document.body.setAttribute("role", "application");
1374 	}
1375 
1376 	function disableBackspaceIntoATable() {
1377 		editor.onKeyDown.add(function(editor, e) {
1378 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1379 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1380 					var previousSibling = selection.getNode().previousSibling;
1381 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
1382 						return tinymce.dom.Event.cancel(e);
1383 					}
1384 				}
1385 			}
1386 		})
1387 	}
1388 
1389 	function addNewLinesBeforeBrInPre() {
1390 		// IE8+ rendering mode does the right thing with BR in PRE
1391 		if (getDocumentMode() > 7) {
1392 			return;
1393 		}
1394 
1395 		 // Enable display: none in area and add a specific class that hides all BR elements in PRE to
1396 		 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
1397 		setEditorCommandState('RespectVisibilityInDesign', true);
1398 		editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
1399 		dom.addClass(editor.getBody(), 'mceHideBrInPre');
1400 
1401 		// Adds a \n before all BR elements in PRE to get them visual
1402 		editor.parser.addNodeFilter('pre', function(nodes, name) {
1403 			var i = nodes.length, brNodes, j, brElm, sibling;
1404 
1405 			while (i--) {
1406 				brNodes = nodes[i].getAll('br');
1407 				j = brNodes.length;
1408 				while (j--) {
1409 					brElm = brNodes[j];
1410 
1411 					// Add \n before BR in PRE elements on older IE:s so the new lines get rendered
1412 					sibling = brElm.prev;
1413 					if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
1414 						sibling.value += '\n';
1415 					} else {
1416 						brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n';
1417 					}
1418 				}
1419 			}
1420 		});
1421 
1422 		// Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
1423 		editor.serializer.addNodeFilter('pre', function(nodes, name) {
1424 			var i = nodes.length, brNodes, j, brElm, sibling;
1425 
1426 			while (i--) {
1427 				brNodes = nodes[i].getAll('br');
1428 				j = brNodes.length;
1429 				while (j--) {
1430 					brElm = brNodes[j];
1431 					sibling = brElm.prev;
1432 					if (sibling && sibling.type == 3) {
1433 						sibling.value = sibling.value.replace(/\r?\n$/, '');
1434 					}
1435 				}
1436 			}
1437 		});
1438 	}
1439 
1440 	function removePreSerializedStylesWhenSelectingControls() {
1441 		dom.bind(editor.getBody(), 'mouseup', function(e) {
1442 			var value, node = selection.getNode();
1443 
1444 			// Moved styles to attributes on IMG eements
1445 			if (node.nodeName == 'IMG') {
1446 				// Convert style width to width attribute
1447 				if (value = dom.getStyle(node, 'width')) {
1448 					dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
1449 					dom.setStyle(node, 'width', '');
1450 				}
1451 
1452 				// Convert style height to height attribute
1453 				if (value = dom.getStyle(node, 'height')) {
1454 					dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
1455 					dom.setStyle(node, 'height', '');
1456 				}
1457 			}
1458 		});
1459 	}
1460 
1461 	function keepInlineElementOnDeleteBackspace() {
1462 		editor.onKeyDown.add(function(editor, e) {
1463 			var isDelete, rng, container, offset, brElm, sibling, collapsed;
1464 
1465 			isDelete = e.keyCode == DELETE;
1466 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1467 				rng = selection.getRng();
1468 				container = rng.startContainer;
1469 				offset = rng.startOffset;
1470 				collapsed = rng.collapsed;
1471 
1472 				// Override delete if the start container is a text node and is at the beginning of text or
1473 				// just before/after the last character to be deleted in collapsed mode
1474 				if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) {
1475 					nonEmptyElements = editor.schema.getNonEmptyElements();
1476 
1477 					// Prevent default logic since it's broken
1478 					e.preventDefault();
1479 
1480 					// Insert a BR before the text node this will prevent the containing element from being deleted/converted
1481 					brElm = dom.create('br', {id: '__tmp'});
1482 					container.parentNode.insertBefore(brElm, container);
1483 
1484 					// Do the browser delete
1485 					editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1486 
1487 					// Check if the previous sibling is empty after deleting for example: <p><b></b>|</p>
1488 					container = selection.getRng().startContainer;
1489 					sibling = container.previousSibling;
1490 					if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) {
1491 						dom.remove(sibling);
1492 					}
1493 
1494 					// Remove the temp element we inserted
1495 					dom.remove('__tmp');
1496 				}
1497 			}
1498 		});
1499 	}
1500 
1501 	function removeBlockQuoteOnBackSpace() {
1502 		// Add block quote deletion handler
1503 		editor.onKeyDown.add(function(editor, e) {
1504 			var rng, container, offset, root, parent;
1505 
1506 			if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) {
1507 				return;
1508 			}
1509 
1510 			rng = selection.getRng();
1511 			container = rng.startContainer;
1512 			offset = rng.startOffset;
1513 			root = dom.getRoot();
1514 			parent = container;
1515 
1516 			if (!rng.collapsed || offset !== 0) {
1517 				return;
1518 			}
1519 
1520 			while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
1521 				parent = parent.parentNode;
1522 			}
1523 
1524 			// Is the cursor at the beginning of a blockquote?
1525 			if (parent.tagName === 'BLOCKQUOTE') {
1526 				// Remove the blockquote
1527 				editor.formatter.toggle('blockquote', null, parent);
1528 
1529 				// Move the caret to the beginning of container
1530 				rng.setStart(container, 0);
1531 				rng.setEnd(container, 0);
1532 				selection.setRng(rng);
1533 				selection.collapse(false);
1534 			}
1535 		});
1536 	};
1537 
1538 	function setGeckoEditingOptions() {
1539 		function setOpts() {
1540 			editor._refreshContentEditable();
1541 
1542 			setEditorCommandState("StyleWithCSS", false);
1543 			setEditorCommandState("enableInlineTableEditing", false);
1544 
1545 			if (!settings.object_resizing) {
1546 				setEditorCommandState("enableObjectResizing", false);
1547 			}
1548 		};
1549 
1550 		if (!settings.readonly) {
1551 			editor.onBeforeExecCommand.add(setOpts);
1552 			editor.onMouseDown.add(setOpts);
1553 		}
1554 	};
1555 
1556 	function addBrAfterLastLinks() {
1557 		function fixLinks(editor, o) {
1558 			tinymce.each(dom.select('a'), function(node) {
1559 				var parentNode = node.parentNode, root = dom.getRoot();
1560 
1561 				if (parentNode.lastChild === node) {
1562 					while (parentNode && !dom.isBlock(parentNode)) {
1563 						if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
1564 							return;
1565 						}
1566 
1567 						parentNode = parentNode.parentNode;
1568 					}
1569 
1570 					dom.add(parentNode, 'br', {'data-mce-bogus' : 1});
1571 				}
1572 			});
1573 		};
1574 
1575 		editor.onExecCommand.add(function(editor, cmd) {
1576 			if (cmd === 'CreateLink') {
1577 				fixLinks(editor);
1578 			}
1579 		});
1580 
1581 		editor.onSetContent.add(selection.onSetContent.add(fixLinks));
1582 	};
1583 
1584 	function setDefaultBlockType() {
1585 		if (settings.forced_root_block) {
1586 			editor.onInit.add(function() {
1587 				setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
1588 			});
1589 		}
1590 	}
1591 
1592 	function removeGhostSelection() {
1593 		function repaint(sender, args) {
1594 			if (!sender || !args.initial) {
1595 				editor.execCommand('mceRepaint');
1596 			}
1597 		};
1598 
1599 		editor.onUndo.add(repaint);
1600 		editor.onRedo.add(repaint);
1601 		editor.onSetContent.add(repaint);
1602 	};
1603 
1604 	function deleteControlItemOnBackSpace() {
1605 		editor.onKeyDown.add(function(editor, e) {
1606 			var rng;
1607 
1608 			if (!e.isDefaultPrevented() && e.keyCode == BACKSPACE) {
1609 				rng = editor.getDoc().selection.createRange();
1610 				if (rng && rng.item) {
1611 					e.preventDefault();
1612 					editor.undoManager.beforeChange();
1613 					dom.remove(rng.item(0));
1614 					editor.undoManager.add();
1615 				}
1616 			}
1617 		});
1618 	};
1619 
1620 	function renderEmptyBlocksFix() {
1621 		var emptyBlocksCSS;
1622 
1623 		// IE10+
1624 		if (getDocumentMode() >= 10) {
1625 			emptyBlocksCSS = '';
1626 			tinymce.each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
1627 				emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
1628 			});
1629 
1630 			editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
1631 		}
1632 	};
1633 
1634 	function fakeImageResize() {
1635 		var mouseDownImg, startX, startY, startW, startH;
1636 
1637 		if (!settings.object_resizing || settings.webkit_fake_resize === false) {
1638 			return;
1639 		}
1640 
1641 		editor.contentStyles.push('.mceResizeImages img {cursor: se-resize !important}');
1642 
1643 		function resizeImage(e) {
1644 			var deltaX, deltaY, ratio, width, height;
1645 
1646 			if (mouseDownImg) {
1647 				deltaX = e.screenX - startX;
1648 				deltaY = e.screenY - startY;
1649 				ratio = Math.max((startW + deltaX) / startW, (startH + deltaY) / startH);
1650 
1651 				// Only update styles if the user draged one pixel or more
1652 				if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) {
1653 					// Constrain proportions
1654 					width = Math.round(startW * ratio);
1655 					height = Math.round(startH * ratio);
1656 
1657 					// Resize by using style or attribute
1658 					if (mouseDownImg.style.width) {
1659 						dom.setStyle(mouseDownImg, 'width', width);
1660 					} else {
1661 						dom.setAttrib(mouseDownImg, 'width', width);
1662 					}
1663 
1664 					// Resize by using style or attribute
1665 					if (mouseDownImg.style.height) {
1666 						dom.setStyle(mouseDownImg, 'height', height);
1667 					} else {
1668 						dom.setAttrib(mouseDownImg, 'height', height);
1669 					}
1670 
1671 					if (!dom.hasClass(editor.getBody(), 'mceResizeImages')) {
1672 						dom.addClass(editor.getBody(), 'mceResizeImages');
1673 					}
1674 				}
1675 			}
1676 		};
1677 
1678 		editor.onMouseDown.add(function(editor, e) {
1679 			var target = e.target;
1680 
1681 			if (target.nodeName == "IMG") {
1682 				mouseDownImg = target;
1683 				startX = e.screenX;
1684 				startY = e.screenY;
1685 				startW = mouseDownImg.clientWidth;
1686 				startH = mouseDownImg.clientHeight;
1687 				dom.bind(editor.getDoc(), 'mousemove', resizeImage);
1688 				e.preventDefault();
1689 			}
1690 		});
1691 
1692 		// Unbind events on node change and restore resize cursor
1693 		editor.onNodeChange.add(function() {
1694 			if (mouseDownImg) {
1695 				mouseDownImg = null;
1696 				dom.unbind(editor.getDoc(), 'mousemove', resizeImage);
1697 			}
1698 
1699 			if (selection.getNode().nodeName == "IMG") {
1700 				dom.addClass(editor.getBody(), 'mceResizeImages');
1701 			} else {
1702 				dom.removeClass(editor.getBody(), 'mceResizeImages');
1703 			}
1704 		});
1705 	};
1706 
1707 	// All browsers
1708 	disableBackspaceIntoATable();
1709 	removeBlockQuoteOnBackSpace();
1710 	emptyEditorWhenDeleting();
1711 
1712 	// WebKit
1713 	if (tinymce.isWebKit) {
1714 		keepInlineElementOnDeleteBackspace();
1715 		cleanupStylesWhenDeleting();
1716 		inputMethodFocus();
1717 		selectControlElements();
1718 		setDefaultBlockType();
1719 
1720 		// iOS
1721 		if (tinymce.isIDevice) {
1722 			selectionChangeNodeChanged();
1723 		} else {
1724 			fakeImageResize();
1725 			selectAll();
1726 		}
1727 	}
1728 
1729 	// IE
1730 	if (tinymce.isIE) {
1731 		removeHrOnBackspace();
1732 		ensureBodyHasRoleApplication();
1733 		addNewLinesBeforeBrInPre();
1734 		removePreSerializedStylesWhenSelectingControls();
1735 		deleteControlItemOnBackSpace();
1736 		renderEmptyBlocksFix();
1737 	}
1738 
1739 	// Gecko
1740 	if (tinymce.isGecko) {
1741 		removeHrOnBackspace();
1742 		focusBody();
1743 		removeStylesWhenDeletingAccrossBlockElements();
1744 		setGeckoEditingOptions();
1745 		addBrAfterLastLinks();
1746 		removeGhostSelection();
1747 	}
1748 };
1749 (function(tinymce) {
1750 	var namedEntities, baseEntities, reverseEntities,
1751 		attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1752 		textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1753 		rawCharsRegExp = /[<>&\"\']/g,
1754 		entityRegExp = /&(#x|#)?([\w]+);/g,
1755 		asciiMap = {
1756 				128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
1757 				135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
1758 				142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
1759 				150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
1760 				156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
1761 		};
1762 
1763 	// Raw entities
1764 	baseEntities = {
1765 		'\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code
1766 		"'" : ''',
1767 		'<' : '<',
1768 		'>' : '>',
1769 		'&' : '&'
1770 	};
1771 
1772 	// Reverse lookup table for raw entities
1773 	reverseEntities = {
1774 		'<' : '<',
1775 		'>' : '>',
1776 		'&' : '&',
1777 		'"' : '"',
1778 		''' : "'"
1779 	};
1780 
1781 	// Decodes text by using the browser
1782 	function nativeDecode(text) {
1783 		var elm;
1784 
1785 		elm = document.createElement("div");
1786 		elm.innerHTML = text;
1787 
1788 		return elm.textContent || elm.innerText || text;
1789 	};
1790 
1791 	// Build a two way lookup table for the entities
1792 	function buildEntitiesLookup(items, radix) {
1793 		var i, chr, entity, lookup = {};
1794 
1795 		if (items) {
1796 			items = items.split(',');
1797 			radix = radix || 10;
1798 
1799 			// Build entities lookup table
1800 			for (i = 0; i < items.length; i += 2) {
1801 				chr = String.fromCharCode(parseInt(items[i], radix));
1802 
1803 				// Only add non base entities
1804 				if (!baseEntities[chr]) {
1805 					entity = '&' + items[i + 1] + ';';
1806 					lookup[chr] = entity;
1807 					lookup[entity] = chr;
1808 				}
1809 			}
1810 
1811 			return lookup;
1812 		}
1813 	};
1814 
1815 	// Unpack entities lookup where the numbers are in radix 32 to reduce the size
1816 	namedEntities = buildEntitiesLookup(
1817 		'50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
1818 		'5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
1819 		'5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
1820 		'5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
1821 		'68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
1822 		'6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
1823 		'6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
1824 		'75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
1825 		'7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
1826 		'7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
1827 		'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
1828 		'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
1829 		't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
1830 		'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
1831 		'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
1832 		'81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
1833 		'8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
1834 		'8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
1835 		'8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
1836 		'8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
1837 		'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
1838 		'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
1839 		'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
1840 		'80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
1841 		'811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
1842 
1843 	tinymce.html = tinymce.html || {};
1844 
1845 	tinymce.html.Entities = {
1846 		encodeRaw : function(text, attr) {
1847 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1848 				return baseEntities[chr] || chr;
1849 			});
1850 		},
1851 
1852 		encodeAllRaw : function(text) {
1853 			return ('' + text).replace(rawCharsRegExp, function(chr) {
1854 				return baseEntities[chr] || chr;
1855 			});
1856 		},
1857 
1858 		encodeNumeric : function(text, attr) {
1859 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1860 				// Multi byte sequence convert it to a single entity
1861 				if (chr.length > 1)
1862 					return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
1863 
1864 				return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
1865 			});
1866 		},
1867 
1868 		encodeNamed : function(text, attr, entities) {
1869 			entities = entities || namedEntities;
1870 
1871 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1872 				return baseEntities[chr] || entities[chr] || chr;
1873 			});
1874 		},
1875 
1876 		getEncodeFunc : function(name, entities) {
1877 			var Entities = tinymce.html.Entities;
1878 
1879 			entities = buildEntitiesLookup(entities) || namedEntities;
1880 
1881 			function encodeNamedAndNumeric(text, attr) {
1882 				return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1883 					return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
1884 				});
1885 			};
1886 
1887 			function encodeCustomNamed(text, attr) {
1888 				return Entities.encodeNamed(text, attr, entities);
1889 			};
1890 
1891 			// Replace + with , to be compatible with previous TinyMCE versions
1892 			name = tinymce.makeMap(name.replace(/\+/g, ','));
1893 
1894 			// Named and numeric encoder
1895 			if (name.named && name.numeric)
1896 				return encodeNamedAndNumeric;
1897 
1898 			// Named encoder
1899 			if (name.named) {
1900 				// Custom names
1901 				if (entities)
1902 					return encodeCustomNamed;
1903 
1904 				return Entities.encodeNamed;
1905 			}
1906 
1907 			// Numeric
1908 			if (name.numeric)
1909 				return Entities.encodeNumeric;
1910 
1911 			// Raw encoder
1912 			return Entities.encodeRaw;
1913 		},
1914 
1915 		decode : function(text) {
1916 			return text.replace(entityRegExp, function(all, numeric, value) {
1917 				if (numeric) {
1918 					value = parseInt(value, numeric.length === 2 ? 16 : 10);
1919 
1920 					// Support upper UTF
1921 					if (value > 0xFFFF) {
1922 						value -= 0x10000;
1923 
1924 						return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
1925 					} else
1926 						return asciiMap[value] || String.fromCharCode(value);
1927 				}
1928 
1929 				return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
1930 			});
1931 		}
1932 	};
1933 })(tinymce);
1934 
1935 tinymce.html.Styles = function(settings, schema) {
1936 	var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
1937 		urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
1938 		styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
1939 		trimRightRegExp = /\s+$/,
1940 		urlColorRegExp = /rgb/,
1941 		undef, i, encodingLookup = {}, encodingItems;
1942 
1943 	settings = settings || {};
1944 
1945 	encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
1946 	for (i = 0; i < encodingItems.length; i++) {
1947 		encodingLookup[encodingItems[i]] = '\uFEFF' + i;
1948 		encodingLookup['\uFEFF' + i] = encodingItems[i];
1949 	}
1950 
1951 	function toHex(match, r, g, b) {
1952 		function hex(val) {
1953 			val = parseInt(val).toString(16);
1954 
1955 			return val.length > 1 ? val : '0' + val; // 0 -> 00
1956 		};
1957 
1958 		return '#' + hex(r) + hex(g) + hex(b);
1959 	};
1960 
1961 	return {
1962 		toHex : function(color) {
1963 			return color.replace(rgbRegExp, toHex);
1964 		},
1965 
1966 		parse : function(css) {
1967 			var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
1968 
1969 			function compress(prefix, suffix) {
1970 				var top, right, bottom, left;
1971 
1972 				// Get values and check it it needs compressing
1973 				top = styles[prefix + '-top' + suffix];
1974 				if (!top)
1975 					return;
1976 
1977 				right = styles[prefix + '-right' + suffix];
1978 				if (top != right)
1979 					return;
1980 
1981 				bottom = styles[prefix + '-bottom' + suffix];
1982 				if (right != bottom)
1983 					return;
1984 
1985 				left = styles[prefix + '-left' + suffix];
1986 				if (bottom != left)
1987 					return;
1988 
1989 				// Compress
1990 				styles[prefix + suffix] = left;
1991 				delete styles[prefix + '-top' + suffix];
1992 				delete styles[prefix + '-right' + suffix];
1993 				delete styles[prefix + '-bottom' + suffix];
1994 				delete styles[prefix + '-left' + suffix];
1995 			};
1996 
1997 			function canCompress(key) {
1998 				var value = styles[key], i;
1999 
2000 				if (!value || value.indexOf(' ') < 0)
2001 					return;
2002 
2003 				value = value.split(' ');
2004 				i = value.length;
2005 				while (i--) {
2006 					if (value[i] !== value[0])
2007 						return false;
2008 				}
2009 
2010 				styles[key] = value[0];
2011 
2012 				return true;
2013 			};
2014 
2015 			function compress2(target, a, b, c) {
2016 				if (!canCompress(a))
2017 					return;
2018 
2019 				if (!canCompress(b))
2020 					return;
2021 
2022 				if (!canCompress(c))
2023 					return;
2024 
2025 				// Compress
2026 				styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
2027 				delete styles[a];
2028 				delete styles[b];
2029 				delete styles[c];
2030 			};
2031 
2032 			// Encodes the specified string by replacing all \" \' ; : with _<num>
2033 			function encode(str) {
2034 				isEncoded = true;
2035 
2036 				return encodingLookup[str];
2037 			};
2038 
2039 			// Decodes the specified string by replacing all _<num> with it's original value \" \' etc
2040 			// It will also decode the \" \' if keep_slashes is set to fale or omitted
2041 			function decode(str, keep_slashes) {
2042 				if (isEncoded) {
2043 					str = str.replace(/\uFEFF[0-9]/g, function(str) {
2044 						return encodingLookup[str];
2045 					});
2046 				}
2047 
2048 				if (!keep_slashes)
2049 					str = str.replace(/\\([\'\";:])/g, "$1");
2050 
2051 				return str;
2052 			};
2053 
2054 			function processUrl(match, url, url2, url3, str, str2) {
2055 				str = str || str2;
2056 
2057 				if (str) {
2058 					str = decode(str);
2059 
2060 					// Force strings into single quote format
2061 					return "'" + str.replace(/\'/g, "\\'") + "'";
2062 				}
2063 
2064 				url = decode(url || url2 || url3);
2065 
2066 				// Convert the URL to relative/absolute depending on config
2067 				if (urlConverter)
2068 					url = urlConverter.call(urlConverterScope, url, 'style');
2069 
2070 				// Output new URL format
2071 				return "url('" + url.replace(/\'/g, "\\'") + "')";
2072 			};
2073 
2074 			if (css) {
2075 				// Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
2076 				css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
2077 					return str.replace(/[;:]/g, encode);
2078 				});
2079 
2080 				// Parse styles
2081 				while (matches = styleRegExp.exec(css)) {
2082 					name = matches[1].replace(trimRightRegExp, '').toLowerCase();
2083 					value = matches[2].replace(trimRightRegExp, '');
2084 
2085 					if (name && value.length > 0) {
2086 						// Opera will produce 700 instead of bold in their style values
2087 						if (name === 'font-weight' && value === '700')
2088 							value = 'bold';
2089 						else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
2090 							value = value.toLowerCase();		
2091 
2092 						// Convert RGB colors to HEX
2093 						value = value.replace(rgbRegExp, toHex);
2094 
2095 						// Convert URLs and force them into url('value') format
2096 						value = value.replace(urlOrStrRegExp, processUrl);
2097 						styles[name] = isEncoded ? decode(value, true) : value;
2098 					}
2099 
2100 					styleRegExp.lastIndex = matches.index + matches[0].length;
2101 				}
2102 
2103 				// Compress the styles to reduce it's size for example IE will expand styles
2104 				compress("border", "");
2105 				compress("border", "-width");
2106 				compress("border", "-color");
2107 				compress("border", "-style");
2108 				compress("padding", "");
2109 				compress("margin", "");
2110 				compress2('border', 'border-width', 'border-style', 'border-color');
2111 
2112 				// Remove pointless border, IE produces these
2113 				if (styles.border === 'medium none')
2114 					delete styles.border;
2115 			}
2116 
2117 			return styles;
2118 		},
2119 
2120 		serialize : function(styles, element_name) {
2121 			var css = '', name, value;
2122 
2123 			function serializeStyles(name) {
2124 				var styleList, i, l, value;
2125 
2126 				styleList = schema.styles[name];
2127 				if (styleList) {
2128 					for (i = 0, l = styleList.length; i < l; i++) {
2129 						name = styleList[i];
2130 						value = styles[name];
2131 
2132 						if (value !== undef && value.length > 0)
2133 							css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2134 					}
2135 				}
2136 			};
2137 
2138 			// Serialize styles according to schema
2139 			if (element_name && schema && schema.styles) {
2140 				// Serialize global styles and element specific styles
2141 				serializeStyles('*');
2142 				serializeStyles(element_name);
2143 			} else {
2144 				// Output the styles in the order they are inside the object
2145 				for (name in styles) {
2146 					value = styles[name];
2147 
2148 					if (value !== undef && value.length > 0)
2149 						css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2150 				}
2151 			}
2152 
2153 			return css;
2154 		}
2155 	};
2156 };
2157 
2158 (function(tinymce) {
2159 	var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;
2160 
2161 	function split(str, delim) {
2162 		return str.split(delim || ',');
2163 	};
2164 
2165 	function unpack(lookup, data) {
2166 		var key, elements = {};
2167 
2168 		function replace(value) {
2169 			return value.replace(/[A-Z]+/g, function(key) {
2170 				return replace(lookup[key]);
2171 			});
2172 		};
2173 
2174 		// Unpack lookup
2175 		for (key in lookup) {
2176 			if (lookup.hasOwnProperty(key))
2177 				lookup[key] = replace(lookup[key]);
2178 		}
2179 
2180 		// Unpack and parse data into object map
2181 		replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
2182 			attributes = split(attributes, '|');
2183 
2184 			elements[name] = {
2185 				attributes : makeMap(attributes),
2186 				attributesOrder : attributes,
2187 				children : makeMap(children, '|', {'#comment' : {}})
2188 			}
2189 		});
2190 
2191 		return elements;
2192 	};
2193 
2194 	function getHTML5() {
2195 		var html5 = mapCache.html5;
2196 
2197 		if (!html5) {
2198 			html5 = mapCache.html5 = unpack({
2199 					A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2200 					B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' +
2201 						'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr',
2202 					C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' +
2203 						'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' +
2204 						'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
2205 				}, 'html[A|manifest][body|head]' +
2206 					'head[A][base|command|link|meta|noscript|script|style|title]' +
2207 					'title[A][#]' +
2208 					'base[A|href|target][]' +
2209 					'link[A|href|rel|media|type|sizes][]' +
2210 					'meta[A|http-equiv|name|content|charset][]' +
2211 					'style[A|type|media|scoped][#]' +
2212 					'script[A|charset|type|src|defer|async][#]' +
2213 					'noscript[A][C]' +
2214 					'body[A][C]' +
2215 					'section[A][C]' +
2216 					'nav[A][C]' +
2217 					'article[A][C]' +
2218 					'aside[A][C]' +
2219 					'h1[A][B]' +
2220 					'h2[A][B]' +
2221 					'h3[A][B]' +
2222 					'h4[A][B]' +
2223 					'h5[A][B]' +
2224 					'h6[A][B]' +
2225 					'hgroup[A][h1|h2|h3|h4|h5|h6]' +
2226 					'header[A][C]' +
2227 					'footer[A][C]' +
2228 					'address[A][C]' +
2229 					'p[A][B]' +
2230 					'br[A][]' +
2231 					'pre[A][B]' +
2232 					'dialog[A][dd|dt]' +
2233 					'blockquote[A|cite][C]' +
2234 					'ol[A|start|reversed][li]' +
2235 					'ul[A][li]' +
2236 					'li[A|value][C]' +
2237 					'dl[A][dd|dt]' +
2238 					'dt[A][B]' +
2239 					'dd[A][C]' +
2240 					'a[A|href|target|ping|rel|media|type][B]' +
2241 					'em[A][B]' +
2242 					'strong[A][B]' +
2243 					'small[A][B]' +
2244 					'cite[A][B]' +
2245 					'q[A|cite][B]' +
2246 					'dfn[A][B]' +
2247 					'abbr[A][B]' +
2248 					'code[A][B]' +
2249 					'var[A][B]' +
2250 					'samp[A][B]' +
2251 					'kbd[A][B]' +
2252 					'sub[A][B]' +
2253 					'sup[A][B]' +
2254 					'i[A][B]' +
2255 					'b[A][B]' +
2256 					'mark[A][B]' +
2257 					'progress[A|value|max][B]' +
2258 					'meter[A|value|min|max|low|high|optimum][B]' +
2259 					'time[A|datetime][B]' +
2260 					'ruby[A][B|rt|rp]' +
2261 					'rt[A][B]' +
2262 					'rp[A][B]' +
2263 					'bdo[A][B]' +
2264 					'span[A][B]' +
2265 					'ins[A|cite|datetime][B]' +
2266 					'del[A|cite|datetime][B]' +
2267 					'figure[A][C|legend|figcaption]' +
2268 					'figcaption[A][C]' +
2269 					'img[A|alt|src|height|width|usemap|ismap][]' +
2270 					'iframe[A|name|src|height|width|sandbox|seamless][]' +
2271 					'embed[A|src|height|width|type][]' +
2272 					'object[A|data|type|height|width|usemap|name|form|classid][param]' +
2273 					'param[A|name|value][]' +
2274 					'details[A|open][C|legend]' +
2275 					'command[A|type|label|icon|disabled|checked|radiogroup][]' +
2276 					'menu[A|type|label][C|li]' +
2277 					'legend[A][C|B]' +
2278 					'div[A][C]' +
2279 					'source[A|src|type|media][]' +
2280 					'audio[A|src|autobuffer|autoplay|loop|controls][source]' +
2281 					'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +
2282 					'hr[A][]' +
2283 					'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
2284 					'fieldset[A|disabled|form|name][C|legend]' +
2285 					'label[A|form|for][B]' +
2286 					'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' +
2287 						'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' +
2288 					'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
2289 					'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
2290 					'datalist[A][B|option]' +
2291 					'optgroup[A|disabled|label][option]' +
2292 					'option[A|disabled|selected|label|value][]' +
2293 					'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +
2294 					'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +
2295 					'output[A|for|form|name][B]' +
2296 					'canvas[A|width|height][]' +
2297 					'map[A|name][B|C]' +
2298 					'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
2299 					'mathml[A][]' +
2300 					'svg[A][]' +
2301 					'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' +
2302 					'caption[A][C]' +
2303 					'colgroup[A|span][col]' +
2304 					'col[A|span][]' +
2305 					'thead[A][tr]' +
2306 					'tfoot[A][tr]' +
2307 					'tbody[A][tr]' +
2308 					'tr[A][th|td]' +
2309 					'th[A|headers|rowspan|colspan|scope][B]' +
2310 					'td[A|headers|rowspan|colspan][C]' +
2311 					'wbr[A][]'
2312 			);
2313 		}
2314 
2315 		return html5;
2316 	};
2317 
2318 	function getHTML4() {
2319 		var html4 = mapCache.html4;
2320 
2321 		if (!html4) {
2322 			// This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
2323 			html4 = mapCache.html4 = unpack({
2324 				Z : 'H|K|N|O|P',
2325 				Y : 'X|form|R|Q',
2326 				ZG : 'E|span|width|align|char|charoff|valign',
2327 				X : 'p|T|div|U|W|isindex|fieldset|table',
2328 				ZF : 'E|align|char|charoff|valign',
2329 				W : 'pre|hr|blockquote|address|center|noframes',
2330 				ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
2331 				ZD : '[E][S]',
2332 				U : 'ul|ol|dl|menu|dir',
2333 				ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
2334 				T : 'h1|h2|h3|h4|h5|h6',
2335 				ZB : 'X|S|Q',
2336 				S : 'R|P',
2337 				ZA : 'a|G|J|M|O|P',
2338 				R : 'a|H|K|N|O',
2339 				Q : 'noscript|P',
2340 				P : 'ins|del|script',
2341 				O : 'input|select|textarea|label|button',
2342 				N : 'M|L',
2343 				M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
2344 				L : 'sub|sup',
2345 				K : 'J|I',
2346 				J : 'tt|i|b|u|s|strike',
2347 				I : 'big|small|font|basefont',
2348 				H : 'G|F',
2349 				G : 'br|span|bdo',
2350 				F : 'object|applet|img|map|iframe',
2351 				E : 'A|B|C',
2352 				D : 'accesskey|tabindex|onfocus|onblur',
2353 				C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2354 				B : 'lang|xml:lang|dir',
2355 				A : 'id|class|style|title'
2356 			}, 'script[id|charset|type|language|src|defer|xml:space][]' + 
2357 				'style[B|id|type|media|title|xml:space][]' + 
2358 				'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
2359 				'param[id|name|value|valuetype|type][]' + 
2360 				'p[E|align][#|S]' + 
2361 				'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
2362 				'br[A|clear][]' + 
2363 				'span[E][#|S]' + 
2364 				'bdo[A|C|B][#|S]' + 
2365 				'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
2366 				'h1[E|align][#|S]' + 
2367 				'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
2368 				'map[B|C|A|name][X|form|Q|area]' + 
2369 				'h2[E|align][#|S]' + 
2370 				'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
2371 				'h3[E|align][#|S]' + 
2372 				'tt[E][#|S]' + 
2373 				'i[E][#|S]' + 
2374 				'b[E][#|S]' + 
2375 				'u[E][#|S]' + 
2376 				's[E][#|S]' + 
2377 				'strike[E][#|S]' + 
2378 				'big[E][#|S]' + 
2379 				'small[E][#|S]' + 
2380 				'font[A|B|size|color|face][#|S]' + 
2381 				'basefont[id|size|color|face][]' + 
2382 				'em[E][#|S]' + 
2383 				'strong[E][#|S]' + 
2384 				'dfn[E][#|S]' + 
2385 				'code[E][#|S]' + 
2386 				'q[E|cite][#|S]' + 
2387 				'samp[E][#|S]' + 
2388 				'kbd[E][#|S]' + 
2389 				'var[E][#|S]' + 
2390 				'cite[E][#|S]' + 
2391 				'abbr[E][#|S]' + 
2392 				'acronym[E][#|S]' + 
2393 				'sub[E][#|S]' + 
2394 				'sup[E][#|S]' + 
2395 				'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
2396 				'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
2397 				'optgroup[E|disabled|label][option]' + 
2398 				'option[E|selected|disabled|label|value][]' + 
2399 				'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
2400 				'label[E|for|accesskey|onfocus|onblur][#|S]' + 
2401 				'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
2402 				'h4[E|align][#|S]' + 
2403 				'ins[E|cite|datetime][#|Y]' + 
2404 				'h5[E|align][#|S]' + 
2405 				'del[E|cite|datetime][#|Y]' + 
2406 				'h6[E|align][#|S]' + 
2407 				'div[E|align][#|Y]' + 
2408 				'ul[E|type|compact][li]' + 
2409 				'li[E|type|value][#|Y]' + 
2410 				'ol[E|type|compact|start][li]' + 
2411 				'dl[E|compact][dt|dd]' + 
2412 				'dt[E][#|S]' + 
2413 				'dd[E][#|Y]' + 
2414 				'menu[E|compact][li]' + 
2415 				'dir[E|compact][li]' + 
2416 				'pre[E|width|xml:space][#|ZA]' + 
2417 				'hr[E|align|noshade|size|width][]' + 
2418 				'blockquote[E|cite][#|Y]' + 
2419 				'address[E][#|S|p]' + 
2420 				'center[E][#|Y]' + 
2421 				'noframes[E][#|Y]' + 
2422 				'isindex[A|B|prompt][]' + 
2423 				'fieldset[E][#|legend|Y]' + 
2424 				'legend[E|accesskey|align][#|S]' + 
2425 				'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
2426 				'caption[E|align][#|S]' + 
2427 				'col[ZG][]' + 
2428 				'colgroup[ZG][col]' + 
2429 				'thead[ZF][tr]' + 
2430 				'tr[ZF|bgcolor][th|td]' + 
2431 				'th[E|ZE][#|Y]' + 
2432 				'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
2433 				'noscript[E][#|Y]' + 
2434 				'td[E|ZE][#|Y]' + 
2435 				'tfoot[ZF][tr]' + 
2436 				'tbody[ZF][tr]' + 
2437 				'area[E|D|shape|coords|href|nohref|alt|target][]' + 
2438 				'base[id|href|target][]' + 
2439 				'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
2440 			);
2441 		}
2442 
2443 		return html4;
2444 	};
2445 
2446 	tinymce.html.Schema = function(settings) {
2447 		var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
2448 		var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {};
2449 
2450 		// Creates an lookup table map object for the specified option or the default value
2451 		function createLookupTable(option, default_value, extend) {
2452 			var value = settings[option];
2453 
2454 			if (!value) {
2455 				// Get cached default map or make it if needed
2456 				value = mapCache[option];
2457 
2458 				if (!value) {
2459 					value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
2460 					value = tinymce.extend(value, extend);
2461 
2462 					mapCache[option] = value;
2463 				}
2464 			} else {
2465 				// Create custom map
2466 				value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
2467 			}
2468 
2469 			return value;
2470 		};
2471 
2472 		settings = settings || {};
2473 		schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();
2474 
2475 		// Allow all elements and attributes if verify_html is set to false
2476 		if (settings.verify_html === false)
2477 			settings.valid_elements = '*[*]';
2478 
2479 		// Build styles list
2480 		if (settings.valid_styles) {
2481 			validStyles = {};
2482 
2483 			// Convert styles into a rule list
2484 			each(settings.valid_styles, function(value, key) {
2485 				validStyles[key] = tinymce.explode(value);
2486 			});
2487 		}
2488 
2489 		// Setup map objects
2490 		whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea');
2491 		selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
2492 		shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr');
2493 		boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');
2494 		nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);
2495 		blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + 
2496 						'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + 
2497 						'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup');
2498 
2499 		// Converts a wildcard expression string to a regexp for example *a will become /.*a/.
2500 		function patternToRegExp(str) {
2501 			return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
2502 		};
2503 
2504 		// Parses the specified valid_elements string and adds to the current rules
2505 		// This function is a bit hard to read since it's heavily optimized for speed
2506 		function addValidElements(valid_elements) {
2507 			var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
2508 				prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
2509 				elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
2510 				attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
2511 				hasPatternsRegExp = /[*?+]/;
2512 
2513 			if (valid_elements) {
2514 				// Split valid elements into an array with rules
2515 				valid_elements = split(valid_elements);
2516 
2517 				if (elements['@']) {
2518 					globalAttributes = elements['@'].attributes;
2519 					globalAttributesOrder = elements['@'].attributesOrder;
2520 				}
2521 
2522 				// Loop all rules
2523 				for (ei = 0, el = valid_elements.length; ei < el; ei++) {
2524 					// Parse element rule
2525 					matches = elementRuleRegExp.exec(valid_elements[ei]);
2526 					if (matches) {
2527 						// Setup local names for matches
2528 						prefix = matches[1];
2529 						elementName = matches[2];
2530 						outputName = matches[3];
2531 						attrData = matches[4];
2532 
2533 						// Create new attributes and attributesOrder
2534 						attributes = {};
2535 						attributesOrder = [];
2536 
2537 						// Create the new element
2538 						element = {
2539 							attributes : attributes,
2540 							attributesOrder : attributesOrder
2541 						};
2542 
2543 						// Padd empty elements prefix
2544 						if (prefix === '#')
2545 							element.paddEmpty = true;
2546 
2547 						// Remove empty elements prefix
2548 						if (prefix === '-')
2549 							element.removeEmpty = true;
2550 
2551 						// Copy attributes from global rule into current rule
2552 						if (globalAttributes) {
2553 							for (key in globalAttributes)
2554 								attributes[key] = globalAttributes[key];
2555 
2556 							attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
2557 						}
2558 
2559 						// Attributes defined
2560 						if (attrData) {
2561 							attrData = split(attrData, '|');
2562 							for (ai = 0, al = attrData.length; ai < al; ai++) {
2563 								matches = attrRuleRegExp.exec(attrData[ai]);
2564 								if (matches) {
2565 									attr = {};
2566 									attrType = matches[1];
2567 									attrName = matches[2].replace(/::/g, ':');
2568 									prefix = matches[3];
2569 									value = matches[4];
2570 
2571 									// Required
2572 									if (attrType === '!') {
2573 										element.attributesRequired = element.attributesRequired || [];
2574 										element.attributesRequired.push(attrName);
2575 										attr.required = true;
2576 									}
2577 
2578 									// Denied from global
2579 									if (attrType === '-') {
2580 										delete attributes[attrName];
2581 										attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
2582 										continue;
2583 									}
2584 
2585 									// Default value
2586 									if (prefix) {
2587 										// Default value
2588 										if (prefix === '=') {
2589 											element.attributesDefault = element.attributesDefault || [];
2590 											element.attributesDefault.push({name: attrName, value: value});
2591 											attr.defaultValue = value;
2592 										}
2593 
2594 										// Forced value
2595 										if (prefix === ':') {
2596 											element.attributesForced = element.attributesForced || [];
2597 											element.attributesForced.push({name: attrName, value: value});
2598 											attr.forcedValue = value;
2599 										}
2600 
2601 										// Required values
2602 										if (prefix === '<')
2603 											attr.validValues = makeMap(value, '?');
2604 									}
2605 
2606 									// Check for attribute patterns
2607 									if (hasPatternsRegExp.test(attrName)) {
2608 										element.attributePatterns = element.attributePatterns || [];
2609 										attr.pattern = patternToRegExp(attrName);
2610 										element.attributePatterns.push(attr);
2611 									} else {
2612 										// Add attribute to order list if it doesn't already exist
2613 										if (!attributes[attrName])
2614 											attributesOrder.push(attrName);
2615 
2616 										attributes[attrName] = attr;
2617 									}
2618 								}
2619 							}
2620 						}
2621 
2622 						// Global rule, store away these for later usage
2623 						if (!globalAttributes && elementName == '@') {
2624 							globalAttributes = attributes;
2625 							globalAttributesOrder = attributesOrder;
2626 						}
2627 
2628 						// Handle substitute elements such as b/strong
2629 						if (outputName) {
2630 							element.outputName = elementName;
2631 							elements[outputName] = element;
2632 						}
2633 
2634 						// Add pattern or exact element
2635 						if (hasPatternsRegExp.test(elementName)) {
2636 							element.pattern = patternToRegExp(elementName);
2637 							patternElements.push(element);
2638 						} else
2639 							elements[elementName] = element;
2640 					}
2641 				}
2642 			}
2643 		};
2644 
2645 		function setValidElements(valid_elements) {
2646 			elements = {};
2647 			patternElements = [];
2648 
2649 			addValidElements(valid_elements);
2650 
2651 			each(schemaItems, function(element, name) {
2652 				children[name] = element.children;
2653 			});
2654 		};
2655 
2656 		// Adds custom non HTML elements to the schema
2657 		function addCustomElements(custom_elements) {
2658 			var customElementRegExp = /^(~)?(.+)$/;
2659 
2660 			if (custom_elements) {
2661 				each(split(custom_elements), function(rule) {
2662 					var matches = customElementRegExp.exec(rule),
2663 						inline = matches[1] === '~',
2664 						cloneName = inline ? 'span' : 'div',
2665 						name = matches[2];
2666 
2667 					children[name] = children[cloneName];
2668 					customElementsMap[name] = cloneName;
2669 
2670 					// If it's not marked as inline then add it to valid block elements
2671 					if (!inline)
2672 						blockElementsMap[name] = {};
2673 
2674 					// Add custom elements at span/div positions
2675 					each(children, function(element, child) {
2676 						if (element[cloneName])
2677 							element[name] = element[cloneName];
2678 					});
2679 				});
2680 			}
2681 		};
2682 
2683 		// Adds valid children to the schema object
2684 		function addValidChildren(valid_children) {
2685 			var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
2686 
2687 			if (valid_children) {
2688 				each(split(valid_children), function(rule) {
2689 					var matches = childRuleRegExp.exec(rule), parent, prefix;
2690 
2691 					if (matches) {
2692 						prefix = matches[1];
2693 
2694 						// Add/remove items from default
2695 						if (prefix)
2696 							parent = children[matches[2]];
2697 						else
2698 							parent = children[matches[2]] = {'#comment' : {}};
2699 
2700 						parent = children[matches[2]];
2701 
2702 						each(split(matches[3], '|'), function(child) {
2703 							if (prefix === '-')
2704 								delete parent[child];
2705 							else
2706 								parent[child] = {};
2707 						});
2708 					}
2709 				});
2710 			}
2711 		};
2712 
2713 		function getElementRule(name) {
2714 			var element = elements[name], i;
2715 
2716 			// Exact match found
2717 			if (element)
2718 				return element;
2719 
2720 			// No exact match then try the patterns
2721 			i = patternElements.length;
2722 			while (i--) {
2723 				element = patternElements[i];
2724 
2725 				if (element.pattern.test(name))
2726 					return element;
2727 			}
2728 		};
2729 
2730 		if (!settings.valid_elements) {
2731 			// No valid elements defined then clone the elements from the schema spec
2732 			each(schemaItems, function(element, name) {
2733 				elements[name] = {
2734 					attributes : element.attributes,
2735 					attributesOrder : element.attributesOrder
2736 				};
2737 
2738 				children[name] = element.children;
2739 			});
2740 
2741 			// Switch these on HTML4
2742 			if (settings.schema != "html5") {
2743 				each(split('strong/b,em/i'), function(item) {
2744 					item = split(item, '/');
2745 					elements[item[1]].outputName = item[0];
2746 				});
2747 			}
2748 
2749 			// Add default alt attribute for images
2750 			elements.img.attributesDefault = [{name: 'alt', value: ''}];
2751 
2752 			// Remove these if they are empty by default
2753 			each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) {
2754 				if (elements[name]) {
2755 					elements[name].removeEmpty = true;
2756 				}
2757 			});
2758 
2759 			// Padd these by default
2760 			each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
2761 				elements[name].paddEmpty = true;
2762 			});
2763 		} else
2764 			setValidElements(settings.valid_elements);
2765 
2766 		addCustomElements(settings.custom_elements);
2767 		addValidChildren(settings.valid_children);
2768 		addValidElements(settings.extended_valid_elements);
2769 
2770 		// Todo: Remove this when we fix list handling to be valid
2771 		addValidChildren('+ol[ul|ol],+ul[ul|ol]');
2772 
2773 		// Delete invalid elements
2774 		if (settings.invalid_elements) {
2775 			tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
2776 				if (elements[item])
2777 					delete elements[item];
2778 			});
2779 		}
2780 
2781 		// If the user didn't allow span only allow internal spans
2782 		if (!getElementRule('span'))
2783 			addValidElements('span[!data-mce-type|*]');
2784 
2785 		self.children = children;
2786 
2787 		self.styles = validStyles;
2788 
2789 		self.getBoolAttrs = function() {
2790 			return boolAttrMap;
2791 		};
2792 
2793 		self.getBlockElements = function() {
2794 			return blockElementsMap;
2795 		};
2796 
2797 		self.getShortEndedElements = function() {
2798 			return shortEndedElementsMap;
2799 		};
2800 
2801 		self.getSelfClosingElements = function() {
2802 			return selfClosingElementsMap;
2803 		};
2804 
2805 		self.getNonEmptyElements = function() {
2806 			return nonEmptyElementsMap;
2807 		};
2808 
2809 		self.getWhiteSpaceElements = function() {
2810 			return whiteSpaceElementsMap;
2811 		};
2812 
2813 		self.isValidChild = function(name, child) {
2814 			var parent = children[name];
2815 
2816 			return !!(parent && parent[child]);
2817 		};
2818 
2819 		self.isValid = function(name, attr) {
2820 			var attrPatterns, i, rule = getElementRule(name);
2821 
2822 			// Check if it's a valid element
2823 			if (rule) {
2824 				if (attr) {
2825 					// Check if attribute name exists
2826 					if (rule.attributes[attr]) {
2827 						return true;
2828 					}
2829 
2830 					// Check if attribute matches a regexp pattern
2831 					attrPatterns = rule.attributePatterns;
2832 					if (attrPatterns) {
2833 						i = attrPatterns.length;
2834 						while (i--) {
2835 							if (attrPatterns[i].pattern.test(name)) {
2836 								return true;
2837 							}
2838 						}
2839 					}
2840 				} else {
2841 					return true;
2842 				}
2843 			}
2844 
2845 			// No match
2846 			return false;
2847 		};
2848 		
2849 		self.getElementRule = getElementRule;
2850 
2851 		self.getCustomElements = function() {
2852 			return customElementsMap;
2853 		};
2854 
2855 		self.addValidElements = addValidElements;
2856 
2857 		self.setValidElements = setValidElements;
2858 
2859 		self.addCustomElements = addCustomElements;
2860 
2861 		self.addValidChildren = addValidChildren;
2862 	};
2863 })(tinymce);
2864 
2865 (function(tinymce) {
2866 	tinymce.html.SaxParser = function(settings, schema) {
2867 		var self = this, noop = function() {};
2868 
2869 		settings = settings || {};
2870 		self.schema = schema = schema || new tinymce.html.Schema();
2871 
2872 		if (settings.fix_self_closing !== false)
2873 			settings.fix_self_closing = true;
2874 
2875 		// Add handler functions from settings and setup default handlers
2876 		tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
2877 			if (name)
2878 				self[name] = settings[name] || noop;
2879 		});
2880 
2881 		self.parse = function(html) {
2882 			var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
2883 				shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
2884 				validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
2885 				tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
2886 
2887 			function processEndTag(name) {
2888 				var pos, i;
2889 
2890 				// Find position of parent of the same type
2891 				pos = stack.length;
2892 				while (pos--) {
2893 					if (stack[pos].name === name)
2894 						break;						
2895 				}
2896 
2897 				// Found parent
2898 				if (pos >= 0) {
2899 					// Close all the open elements
2900 					for (i = stack.length - 1; i >= pos; i--) {
2901 						name = stack[i];
2902 
2903 						if (name.valid)
2904 							self.end(name.name);
2905 					}
2906 
2907 					// Remove the open elements from the stack
2908 					stack.length = pos;
2909 				}
2910 			};
2911 
2912 			function parseAttribute(match, name, value, val2, val3) {
2913 				var attrRule, i;
2914 
2915 				name = name.toLowerCase();
2916 				value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
2917 
2918 				// Validate name and value
2919 				if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
2920 					attrRule = validAttributesMap[name];
2921 
2922 					// Find rule by pattern matching
2923 					if (!attrRule && validAttributePatterns) {
2924 						i = validAttributePatterns.length;
2925 						while (i--) {
2926 							attrRule = validAttributePatterns[i];
2927 							if (attrRule.pattern.test(name))
2928 								break;
2929 						}
2930 
2931 						// No rule matched
2932 						if (i === -1)
2933 							attrRule = null;
2934 					}
2935 
2936 					// No attribute rule found
2937 					if (!attrRule)
2938 						return;
2939 
2940 					// Validate value
2941 					if (attrRule.validValues && !(value in attrRule.validValues))
2942 						return;
2943 				}
2944 
2945 				// Add attribute to list and map
2946 				attrList.map[name] = value;
2947 				attrList.push({
2948 					name: name,
2949 					value: value
2950 				});
2951 			};
2952 
2953 			// Precompile RegExps and map objects
2954 			tokenRegExp = new RegExp('<(?:' +
2955 				'(?:!--([\\w\\W]*?)-->)|' + // Comment
2956 				'(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
2957 				'(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
2958 				'(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
2959 				'(?:\\/([^>]+)>)|' + // End element
2960 				'(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
2961 			')', 'g');
2962 
2963 			attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
2964 			specialElements = {
2965 				'script' : /<\/script[^>]*>/gi,
2966 				'style' : /<\/style[^>]*>/gi,
2967 				'noscript' : /<\/noscript[^>]*>/gi
2968 			};
2969 
2970 			// Setup lookup tables for empty elements and boolean attributes
2971 			shortEndedElements = schema.getShortEndedElements();
2972 			selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
2973 			fillAttrsMap = schema.getBoolAttrs();
2974 			validate = settings.validate;
2975 			removeInternalElements = settings.remove_internals;
2976 			fixSelfClosing = settings.fix_self_closing;
2977 			isIE = tinymce.isIE;
2978 			invalidPrefixRegExp = /^:/;
2979 
2980 			while (matches = tokenRegExp.exec(html)) {
2981 				// Text
2982 				if (index < matches.index)
2983 					self.text(decode(html.substr(index, matches.index - index)));
2984 
2985 				if (value = matches[6]) { // End element
2986 					value = value.toLowerCase();
2987 
2988 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
2989 					if (isIE && invalidPrefixRegExp.test(value))
2990 						value = value.substr(1);
2991 
2992 					processEndTag(value);
2993 				} else if (value = matches[7]) { // Start element
2994 					value = value.toLowerCase();
2995 
2996 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
2997 					if (isIE && invalidPrefixRegExp.test(value))
2998 						value = value.substr(1);
2999 
3000 					isShortEnded = value in shortEndedElements;
3001 
3002 					// Is self closing tag for example an <li> after an open <li>
3003 					if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
3004 						processEndTag(value);
3005 
3006 					// Validate element
3007 					if (!validate || (elementRule = schema.getElementRule(value))) {
3008 						isValidElement = true;
3009 
3010 						// Grab attributes map and patters when validation is enabled
3011 						if (validate) {
3012 							validAttributesMap = elementRule.attributes;
3013 							validAttributePatterns = elementRule.attributePatterns;
3014 						}
3015 
3016 						// Parse attributes
3017 						if (attribsValue = matches[8]) {
3018 							isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
3019 
3020 							// If the element has internal attributes then remove it if we are told to do so
3021 							if (isInternalElement && removeInternalElements)
3022 								isValidElement = false;
3023 
3024 							attrList = [];
3025 							attrList.map = {};
3026 
3027 							attribsValue.replace(attrRegExp, parseAttribute);
3028 						} else {
3029 							attrList = [];
3030 							attrList.map = {};
3031 						}
3032 
3033 						// Process attributes if validation is enabled
3034 						if (validate && !isInternalElement) {
3035 							attributesRequired = elementRule.attributesRequired;
3036 							attributesDefault = elementRule.attributesDefault;
3037 							attributesForced = elementRule.attributesForced;
3038 
3039 							// Handle forced attributes
3040 							if (attributesForced) {
3041 								i = attributesForced.length;
3042 								while (i--) {
3043 									attr = attributesForced[i];
3044 									name = attr.name;
3045 									attrValue = attr.value;
3046 
3047 									if (attrValue === '{$uid}')
3048 										attrValue = 'mce_' + idCount++;
3049 
3050 									attrList.map[name] = attrValue;
3051 									attrList.push({name: name, value: attrValue});
3052 								}
3053 							}
3054 
3055 							// Handle default attributes
3056 							if (attributesDefault) {
3057 								i = attributesDefault.length;
3058 								while (i--) {
3059 									attr = attributesDefault[i];
3060 									name = attr.name;
3061 
3062 									if (!(name in attrList.map)) {
3063 										attrValue = attr.value;
3064 
3065 										if (attrValue === '{$uid}')
3066 											attrValue = 'mce_' + idCount++;
3067 
3068 										attrList.map[name] = attrValue;
3069 										attrList.push({name: name, value: attrValue});
3070 									}
3071 								}
3072 							}
3073 
3074 							// Handle required attributes
3075 							if (attributesRequired) {
3076 								i = attributesRequired.length;
3077 								while (i--) {
3078 									if (attributesRequired[i] in attrList.map)
3079 										break;
3080 								}
3081 
3082 								// None of the required attributes where found
3083 								if (i === -1)
3084 									isValidElement = false;
3085 							}
3086 
3087 							// Invalidate element if it's marked as bogus
3088 							if (attrList.map['data-mce-bogus'])
3089 								isValidElement = false;
3090 						}
3091 
3092 						if (isValidElement)
3093 							self.start(value, attrList, isShortEnded);
3094 					} else
3095 						isValidElement = false;
3096 
3097 					// Treat script, noscript and style a bit different since they may include code that looks like elements
3098 					if (endRegExp = specialElements[value]) {
3099 						endRegExp.lastIndex = index = matches.index + matches[0].length;
3100 
3101 						if (matches = endRegExp.exec(html)) {
3102 							if (isValidElement)
3103 								text = html.substr(index, matches.index - index);
3104 
3105 							index = matches.index + matches[0].length;
3106 						} else {
3107 							text = html.substr(index);
3108 							index = html.length;
3109 						}
3110 
3111 						if (isValidElement && text.length > 0)
3112 							self.text(text, true);
3113 
3114 						if (isValidElement)
3115 							self.end(value);
3116 
3117 						tokenRegExp.lastIndex = index;
3118 						continue;
3119 					}
3120 
3121 					// Push value on to stack
3122 					if (!isShortEnded) {
3123 						if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
3124 							stack.push({name: value, valid: isValidElement});
3125 						else if (isValidElement)
3126 							self.end(value);
3127 					}
3128 				} else if (value = matches[1]) { // Comment
3129 					self.comment(value);
3130 				} else if (value = matches[2]) { // CDATA
3131 					self.cdata(value);
3132 				} else if (value = matches[3]) { // DOCTYPE
3133 					self.doctype(value);
3134 				} else if (value = matches[4]) { // PI
3135 					self.pi(value, matches[5]);
3136 				}
3137 
3138 				index = matches.index + matches[0].length;
3139 			}
3140 
3141 			// Text
3142 			if (index < html.length)
3143 				self.text(decode(html.substr(index)));
3144 
3145 			// Close any open elements
3146 			for (i = stack.length - 1; i >= 0; i--) {
3147 				value = stack[i];
3148 
3149 				if (value.valid)
3150 					self.end(value.name);
3151 			}
3152 		};
3153 	}
3154 })(tinymce);
3155 
3156 (function(tinymce) {
3157 	var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
3158 		'#text' : 3,
3159 		'#comment' : 8,
3160 		'#cdata' : 4,
3161 		'#pi' : 7,
3162 		'#doctype' : 10,
3163 		'#document-fragment' : 11
3164 	};
3165 
3166 	// Walks the tree left/right
3167 	function walk(node, root_node, prev) {
3168 		var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
3169 
3170 		// Walk into nodes if it has a start
3171 		if (node[startName])
3172 			return node[startName];
3173 
3174 		// Return the sibling if it has one
3175 		if (node !== root_node) {
3176 			sibling = node[siblingName];
3177 
3178 			if (sibling)
3179 				return sibling;
3180 
3181 			// Walk up the parents to look for siblings
3182 			for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
3183 				sibling = parent[siblingName];
3184 
3185 				if (sibling)
3186 					return sibling;
3187 			}
3188 		}
3189 	};
3190 
3191 	function Node(name, type) {
3192 		this.name = name;
3193 		this.type = type;
3194 
3195 		if (type === 1) {
3196 			this.attributes = [];
3197 			this.attributes.map = {};
3198 		}
3199 	}
3200 
3201 	tinymce.extend(Node.prototype, {
3202 		replace : function(node) {
3203 			var self = this;
3204 
3205 			if (node.parent)
3206 				node.remove();
3207 
3208 			self.insert(node, self);
3209 			self.remove();
3210 
3211 			return self;
3212 		},
3213 
3214 		attr : function(name, value) {
3215 			var self = this, attrs, i, undef;
3216 
3217 			if (typeof name !== "string") {
3218 				for (i in name)
3219 					self.attr(i, name[i]);
3220 
3221 				return self;
3222 			}
3223 
3224 			if (attrs = self.attributes) {
3225 				if (value !== undef) {
3226 					// Remove attribute
3227 					if (value === null) {
3228 						if (name in attrs.map) {
3229 							delete attrs.map[name];
3230 
3231 							i = attrs.length;
3232 							while (i--) {
3233 								if (attrs[i].name === name) {
3234 									attrs = attrs.splice(i, 1);
3235 									return self;
3236 								}
3237 							}
3238 						}
3239 
3240 						return self;
3241 					}
3242 
3243 					// Set attribute
3244 					if (name in attrs.map) {
3245 						// Set attribute
3246 						i = attrs.length;
3247 						while (i--) {
3248 							if (attrs[i].name === name) {
3249 								attrs[i].value = value;
3250 								break;
3251 							}
3252 						}
3253 					} else
3254 						attrs.push({name: name, value: value});
3255 
3256 					attrs.map[name] = value;
3257 
3258 					return self;
3259 				} else {
3260 					return attrs.map[name];
3261 				}
3262 			}
3263 		},
3264 
3265 		clone : function() {
3266 			var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
3267 
3268 			// Clone element attributes
3269 			if (selfAttrs = self.attributes) {
3270 				cloneAttrs = [];
3271 				cloneAttrs.map = {};
3272 
3273 				for (i = 0, l = selfAttrs.length; i < l; i++) {
3274 					selfAttr = selfAttrs[i];
3275 
3276 					// Clone everything except id
3277 					if (selfAttr.name !== 'id') {
3278 						cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
3279 						cloneAttrs.map[selfAttr.name] = selfAttr.value;
3280 					}
3281 				}
3282 
3283 				clone.attributes = cloneAttrs;
3284 			}
3285 
3286 			clone.value = self.value;
3287 			clone.shortEnded = self.shortEnded;
3288 
3289 			return clone;
3290 		},
3291 
3292 		wrap : function(wrapper) {
3293 			var self = this;
3294 
3295 			self.parent.insert(wrapper, self);
3296 			wrapper.append(self);
3297 
3298 			return self;
3299 		},
3300 
3301 		unwrap : function() {
3302 			var self = this, node, next;
3303 
3304 			for (node = self.firstChild; node; ) {
3305 				next = node.next;
3306 				self.insert(node, self, true);
3307 				node = next;
3308 			}
3309 
3310 			self.remove();
3311 		},
3312 
3313 		remove : function() {
3314 			var self = this, parent = self.parent, next = self.next, prev = self.prev;
3315 
3316 			if (parent) {
3317 				if (parent.firstChild === self) {
3318 					parent.firstChild = next;
3319 
3320 					if (next)
3321 						next.prev = null;
3322 				} else {
3323 					prev.next = next;
3324 				}
3325 
3326 				if (parent.lastChild === self) {
3327 					parent.lastChild = prev;
3328 
3329 					if (prev)
3330 						prev.next = null;
3331 				} else {
3332 					next.prev = prev;
3333 				}
3334 
3335 				self.parent = self.next = self.prev = null;
3336 			}
3337 
3338 			return self;
3339 		},
3340 
3341 		append : function(node) {
3342 			var self = this, last;
3343 
3344 			if (node.parent)
3345 				node.remove();
3346 
3347 			last = self.lastChild;
3348 			if (last) {
3349 				last.next = node;
3350 				node.prev = last;
3351 				self.lastChild = node;
3352 			} else
3353 				self.lastChild = self.firstChild = node;
3354 
3355 			node.parent = self;
3356 
3357 			return node;
3358 		},
3359 
3360 		insert : function(node, ref_node, before) {
3361 			var parent;
3362 
3363 			if (node.parent)
3364 				node.remove();
3365 
3366 			parent = ref_node.parent || this;
3367 
3368 			if (before) {
3369 				if (ref_node === parent.firstChild)
3370 					parent.firstChild = node;
3371 				else
3372 					ref_node.prev.next = node;
3373 
3374 				node.prev = ref_node.prev;
3375 				node.next = ref_node;
3376 				ref_node.prev = node;
3377 			} else {
3378 				if (ref_node === parent.lastChild)
3379 					parent.lastChild = node;
3380 				else
3381 					ref_node.next.prev = node;
3382 
3383 				node.next = ref_node.next;
3384 				node.prev = ref_node;
3385 				ref_node.next = node;
3386 			}
3387 
3388 			node.parent = parent;
3389 
3390 			return node;
3391 		},
3392 
3393 		getAll : function(name) {
3394 			var self = this, node, collection = [];
3395 
3396 			for (node = self.firstChild; node; node = walk(node, self)) {
3397 				if (node.name === name)
3398 					collection.push(node);
3399 			}
3400 
3401 			return collection;
3402 		},
3403 
3404 		empty : function() {
3405 			var self = this, nodes, i, node;
3406 
3407 			// Remove all children
3408 			if (self.firstChild) {
3409 				nodes = [];
3410 
3411 				// Collect the children
3412 				for (node = self.firstChild; node; node = walk(node, self))
3413 					nodes.push(node);
3414 
3415 				// Remove the children
3416 				i = nodes.length;
3417 				while (i--) {
3418 					node = nodes[i];
3419 					node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
3420 				}
3421 			}
3422 
3423 			self.firstChild = self.lastChild = null;
3424 
3425 			return self;
3426 		},
3427 
3428 		isEmpty : function(elements) {
3429 			var self = this, node = self.firstChild, i, name;
3430 
3431 			if (node) {
3432 				do {
3433 					if (node.type === 1) {
3434 						// Ignore bogus elements
3435 						if (node.attributes.map['data-mce-bogus'])
3436 							continue;
3437 
3438 						// Keep empty elements like <img />
3439 						if (elements[node.name])
3440 							return false;
3441 
3442 						// Keep elements with data attributes or name attribute like <a name="1"></a>
3443 						i = node.attributes.length;
3444 						while (i--) {
3445 							name = node.attributes[i].name;
3446 							if (name === "name" || name.indexOf('data-') === 0)
3447 								return false;
3448 						}
3449 					}
3450 
3451 					// Keep comments
3452 					if (node.type === 8)
3453 						return false;
3454 					
3455 					// Keep non whitespace text nodes
3456 					if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
3457 						return false;
3458 				} while (node = walk(node, self));
3459 			}
3460 
3461 			return true;
3462 		},
3463 
3464 		walk : function(prev) {
3465 			return walk(this, null, prev);
3466 		}
3467 	});
3468 
3469 	tinymce.extend(Node, {
3470 		create : function(name, attrs) {
3471 			var node, attrName;
3472 
3473 			// Create node
3474 			node = new Node(name, typeLookup[name] || 1);
3475 
3476 			// Add attributes if needed
3477 			if (attrs) {
3478 				for (attrName in attrs)
3479 					node.attr(attrName, attrs[attrName]);
3480 			}
3481 
3482 			return node;
3483 		}
3484 	});
3485 
3486 	tinymce.html.Node = Node;
3487 })(tinymce);
3488 
3489 (function(tinymce) {
3490 	var Node = tinymce.html.Node;
3491 
3492 	tinymce.html.DomParser = function(settings, schema) {
3493 		var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
3494 
3495 		settings = settings || {};
3496 		settings.validate = "validate" in settings ? settings.validate : true;
3497 		settings.root_name = settings.root_name || 'body';
3498 		self.schema = schema = schema || new tinymce.html.Schema();
3499 
3500 		function fixInvalidChildren(nodes) {
3501 			var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
3502 				childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
3503 
3504 			nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
3505 			nonEmptyElements = schema.getNonEmptyElements();
3506 
3507 			for (ni = 0; ni < nodes.length; ni++) {
3508 				node = nodes[ni];
3509 
3510 				// Already removed
3511 				if (!node.parent)
3512 					continue;
3513 
3514 				// Get list of all parent nodes until we find a valid parent to stick the child into
3515 				parents = [node];
3516 				for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
3517 					parents.push(parent);
3518 
3519 				// Found a suitable parent
3520 				if (parent && parents.length > 1) {
3521 					// Reverse the array since it makes looping easier
3522 					parents.reverse();
3523 
3524 					// Clone the related parent and insert that after the moved node
3525 					newParent = currentNode = self.filterNode(parents[0].clone());
3526 
3527 					// Start cloning and moving children on the left side of the target node
3528 					for (i = 0; i < parents.length - 1; i++) {
3529 						if (schema.isValidChild(currentNode.name, parents[i].name)) {
3530 							tempNode = self.filterNode(parents[i].clone());
3531 							currentNode.append(tempNode);
3532 						} else
3533 							tempNode = currentNode;
3534 
3535 						for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
3536 							nextNode = childNode.next;
3537 							tempNode.append(childNode);
3538 							childNode = nextNode;
3539 						}
3540 
3541 						currentNode = tempNode;
3542 					}
3543 
3544 					if (!newParent.isEmpty(nonEmptyElements)) {
3545 						parent.insert(newParent, parents[0], true);
3546 						parent.insert(node, newParent);
3547 					} else {
3548 						parent.insert(node, parents[0], true);
3549 					}
3550 
3551 					// Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
3552 					parent = parents[0];
3553 					if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
3554 						parent.empty().remove();
3555 					}
3556 				} else if (node.parent) {
3557 					// If it's an LI try to find a UL/OL for it or wrap it
3558 					if (node.name === 'li') {
3559 						sibling = node.prev;
3560 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3561 							sibling.append(node);
3562 							continue;
3563 						}
3564 
3565 						sibling = node.next;
3566 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3567 							sibling.insert(node, sibling.firstChild, true);
3568 							continue;
3569 						}
3570 
3571 						node.wrap(self.filterNode(new Node('ul', 1)));
3572 						continue;
3573 					}
3574 
3575 					// Try wrapping the element in a DIV
3576 					if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
3577 						node.wrap(self.filterNode(new Node('div', 1)));
3578 					} else {
3579 						// We failed wrapping it, then remove or unwrap it
3580 						if (node.name === 'style' || node.name === 'script')
3581 							node.empty().remove();
3582 						else
3583 							node.unwrap();
3584 					}
3585 				}
3586 			}
3587 		};
3588 
3589 		self.filterNode = function(node) {
3590 			var i, name, list;
3591 
3592 			// Run element filters
3593 			if (name in nodeFilters) {
3594 				list = matchedNodes[name];
3595 
3596 				if (list)
3597 					list.push(node);
3598 				else
3599 					matchedNodes[name] = [node];
3600 			}
3601 
3602 			// Run attribute filters
3603 			i = attributeFilters.length;
3604 			while (i--) {
3605 				name = attributeFilters[i].name;
3606 
3607 				if (name in node.attributes.map) {
3608 					list = matchedAttributes[name];
3609 
3610 					if (list)
3611 						list.push(node);
3612 					else
3613 						matchedAttributes[name] = [node];
3614 				}
3615 			}
3616 
3617 			return node;
3618 		};
3619 
3620 		self.addNodeFilter = function(name, callback) {
3621 			tinymce.each(tinymce.explode(name), function(name) {
3622 				var list = nodeFilters[name];
3623 
3624 				if (!list)
3625 					nodeFilters[name] = list = [];
3626 
3627 				list.push(callback);
3628 			});
3629 		};
3630 
3631 		self.addAttributeFilter = function(name, callback) {
3632 			tinymce.each(tinymce.explode(name), function(name) {
3633 				var i;
3634 
3635 				for (i = 0; i < attributeFilters.length; i++) {
3636 					if (attributeFilters[i].name === name) {
3637 						attributeFilters[i].callbacks.push(callback);
3638 						return;
3639 					}
3640 				}
3641 
3642 				attributeFilters.push({name: name, callbacks: [callback]});
3643 			});
3644 		};
3645 
3646 		self.parse = function(html, args) {
3647 			var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
3648 				blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement,
3649 				endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
3650 
3651 			args = args || {};
3652 			matchedNodes = {};
3653 			matchedAttributes = {};
3654 			blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
3655 			nonEmptyElements = schema.getNonEmptyElements();
3656 			children = schema.children;
3657 			validate = settings.validate;
3658 			rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
3659 
3660 			whiteSpaceElements = schema.getWhiteSpaceElements();
3661 			startWhiteSpaceRegExp = /^[ \t\r\n]+/;
3662 			endWhiteSpaceRegExp = /[ \t\r\n]+$/;
3663 			allWhiteSpaceRegExp = /[ \t\r\n]+/g;
3664 			isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
3665 
3666 			function addRootBlocks() {
3667 				var node = rootNode.firstChild, next, rootBlockNode;
3668 
3669 				while (node) {
3670 					next = node.next;
3671 
3672 					if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
3673 						if (!rootBlockNode) {
3674 							// Create a new root block element
3675 							rootBlockNode = createNode(rootBlockName, 1);
3676 							rootNode.insert(rootBlockNode, node);
3677 							rootBlockNode.append(node);
3678 						} else
3679 							rootBlockNode.append(node);
3680 					} else {
3681 						rootBlockNode = null;
3682 					}
3683 
3684 					node = next;
3685 				};
3686 			};
3687 
3688 			function createNode(name, type) {
3689 				var node = new Node(name, type), list;
3690 
3691 				if (name in nodeFilters) {
3692 					list = matchedNodes[name];
3693 
3694 					if (list)
3695 						list.push(node);
3696 					else
3697 						matchedNodes[name] = [node];
3698 				}
3699 
3700 				return node;
3701 			};
3702 
3703 			function removeWhitespaceBefore(node) {
3704 				var textNode, textVal, sibling;
3705 
3706 				for (textNode = node.prev; textNode && textNode.type === 3; ) {
3707 					textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
3708 
3709 					if (textVal.length > 0) {
3710 						textNode.value = textVal;
3711 						textNode = textNode.prev;
3712 					} else {
3713 						sibling = textNode.prev;
3714 						textNode.remove();
3715 						textNode = sibling;
3716 					}
3717 				}
3718 			};
3719 
3720 			function cloneAndExcludeBlocks(input) {
3721 				var name, output = {};
3722 
3723 				for (name in input) {
3724 					if (name !== 'li' && name != 'p') {
3725 						output[name] = input[name];
3726 					}
3727 				}
3728 
3729 				return output;
3730 			};
3731 
3732 			parser = new tinymce.html.SaxParser({
3733 				validate : validate,
3734 
3735 				// Exclude P and LI from DOM parsing since it's treated better by the DOM parser
3736 				self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
3737 
3738 				cdata: function(text) {
3739 					node.append(createNode('#cdata', 4)).value = text;
3740 				},
3741 
3742 				text: function(text, raw) {
3743 					var textNode;
3744 
3745 					// Trim all redundant whitespace on non white space elements
3746 					if (!isInWhiteSpacePreservedElement) {
3747 						text = text.replace(allWhiteSpaceRegExp, ' ');
3748 
3749 						if (node.lastChild && blockElements[node.lastChild.name])
3750 							text = text.replace(startWhiteSpaceRegExp, '');
3751 					}
3752 
3753 					// Do we need to create the node
3754 					if (text.length !== 0) {
3755 						textNode = createNode('#text', 3);
3756 						textNode.raw = !!raw;
3757 						node.append(textNode).value = text;
3758 					}
3759 				},
3760 
3761 				comment: function(text) {
3762 					node.append(createNode('#comment', 8)).value = text;
3763 				},
3764 
3765 				pi: function(name, text) {
3766 					node.append(createNode(name, 7)).value = text;
3767 					removeWhitespaceBefore(node);
3768 				},
3769 
3770 				doctype: function(text) {
3771 					var newNode;
3772 		
3773 					newNode = node.append(createNode('#doctype', 10));
3774 					newNode.value = text;
3775 					removeWhitespaceBefore(node);
3776 				},
3777 
3778 				start: function(name, attrs, empty) {
3779 					var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
3780 
3781 					elementRule = validate ? schema.getElementRule(name) : {};
3782 					if (elementRule) {
3783 						newNode = createNode(elementRule.outputName || name, 1);
3784 						newNode.attributes = attrs;
3785 						newNode.shortEnded = empty;
3786 
3787 						node.append(newNode);
3788 
3789 						// Check if node is valid child of the parent node is the child is
3790 						// unknown we don't collect it since it's probably a custom element
3791 						parent = children[node.name];
3792 						if (parent && children[newNode.name] && !parent[newNode.name])
3793 							invalidChildren.push(newNode);
3794 
3795 						attrFiltersLen = attributeFilters.length;
3796 						while (attrFiltersLen--) {
3797 							attrName = attributeFilters[attrFiltersLen].name;
3798 
3799 							if (attrName in attrs.map) {
3800 								list = matchedAttributes[attrName];
3801 
3802 								if (list)
3803 									list.push(newNode);
3804 								else
3805 									matchedAttributes[attrName] = [newNode];
3806 							}
3807 						}
3808 
3809 						// Trim whitespace before block
3810 						if (blockElements[name])
3811 							removeWhitespaceBefore(newNode);
3812 
3813 						// Change current node if the element wasn't empty i.e not <br /> or <img />
3814 						if (!empty)
3815 							node = newNode;
3816 
3817 						// Check if we are inside a whitespace preserved element
3818 						if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
3819 							isInWhiteSpacePreservedElement = true;
3820 						}
3821 					}
3822 				},
3823 
3824 				end: function(name) {
3825 					var textNode, elementRule, text, sibling, tempNode;
3826 
3827 					elementRule = validate ? schema.getElementRule(name) : {};
3828 					if (elementRule) {
3829 						if (blockElements[name]) {
3830 							if (!isInWhiteSpacePreservedElement) {
3831 								// Trim whitespace of the first node in a block
3832 								textNode = node.firstChild;
3833 								if (textNode && textNode.type === 3) {
3834 									text = textNode.value.replace(startWhiteSpaceRegExp, '');
3835 
3836 									// Any characters left after trim or should we remove it
3837 									if (text.length > 0) {
3838 										textNode.value = text;
3839 										textNode = textNode.next;
3840 									} else {
3841 										sibling = textNode.next;
3842 										textNode.remove();
3843 										textNode = sibling;
3844 									}
3845 
3846 									// Remove any pure whitespace siblings
3847 									while (textNode && textNode.type === 3) {
3848 										text = textNode.value;
3849 										sibling = textNode.next;
3850 
3851 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
3852 											textNode.remove();
3853 											textNode = sibling;
3854 										}
3855 
3856 										textNode = sibling;
3857 									}
3858 								}
3859 
3860 								// Trim whitespace of the last node in a block
3861 								textNode = node.lastChild;
3862 								if (textNode && textNode.type === 3) {
3863 									text = textNode.value.replace(endWhiteSpaceRegExp, '');
3864 
3865 									// Any characters left after trim or should we remove it
3866 									if (text.length > 0) {
3867 										textNode.value = text;
3868 										textNode = textNode.prev;
3869 									} else {
3870 										sibling = textNode.prev;
3871 										textNode.remove();
3872 										textNode = sibling;
3873 									}
3874 
3875 									// Remove any pure whitespace siblings
3876 									while (textNode && textNode.type === 3) {
3877 										text = textNode.value;
3878 										sibling = textNode.prev;
3879 
3880 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
3881 											textNode.remove();
3882 											textNode = sibling;
3883 										}
3884 
3885 										textNode = sibling;
3886 									}
3887 								}
3888 							}
3889 
3890 							// Trim start white space
3891 							textNode = node.prev;
3892 							if (textNode && textNode.type === 3) {
3893 								text = textNode.value.replace(startWhiteSpaceRegExp, '');
3894 
3895 								if (text.length > 0)
3896 									textNode.value = text;
3897 								else
3898 									textNode.remove();
3899 							}
3900 						}
3901 
3902 						// Check if we exited a whitespace preserved element
3903 						if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
3904 							isInWhiteSpacePreservedElement = false;
3905 						}
3906 
3907 						// Handle empty nodes
3908 						if (elementRule.removeEmpty || elementRule.paddEmpty) {
3909 							if (node.isEmpty(nonEmptyElements)) {
3910 								if (elementRule.paddEmpty)
3911 									node.empty().append(new Node('#text', '3')).value = '\u00a0';
3912 								else {
3913 									// Leave nodes that have a name like <a name="name">
3914 									if (!node.attributes.map.name && !node.attributes.map.id) {
3915 										tempNode = node.parent;
3916 										node.empty().remove();
3917 										node = tempNode;
3918 										return;
3919 									}
3920 								}
3921 							}
3922 						}
3923 
3924 						node = node.parent;
3925 					}
3926 				}
3927 			}, schema);
3928 
3929 			rootNode = node = new Node(args.context || settings.root_name, 11);
3930 
3931 			parser.parse(html);
3932 
3933 			// Fix invalid children or report invalid children in a contextual parsing
3934 			if (validate && invalidChildren.length) {
3935 				if (!args.context)
3936 					fixInvalidChildren(invalidChildren);
3937 				else
3938 					args.invalid = true;
3939 			}
3940 
3941 			// Wrap nodes in the root into block elements if the root is body
3942 			if (rootBlockName && rootNode.name == 'body')
3943 				addRootBlocks();
3944 
3945 			// Run filters only when the contents is valid
3946 			if (!args.invalid) {
3947 				// Run node filters
3948 				for (name in matchedNodes) {
3949 					list = nodeFilters[name];
3950 					nodes = matchedNodes[name];
3951 
3952 					// Remove already removed children
3953 					fi = nodes.length;
3954 					while (fi--) {
3955 						if (!nodes[fi].parent)
3956 							nodes.splice(fi, 1);
3957 					}
3958 
3959 					for (i = 0, l = list.length; i < l; i++)
3960 						list[i](nodes, name, args);
3961 				}
3962 
3963 				// Run attribute filters
3964 				for (i = 0, l = attributeFilters.length; i < l; i++) {
3965 					list = attributeFilters[i];
3966 
3967 					if (list.name in matchedAttributes) {
3968 						nodes = matchedAttributes[list.name];
3969 
3970 						// Remove already removed children
3971 						fi = nodes.length;
3972 						while (fi--) {
3973 							if (!nodes[fi].parent)
3974 								nodes.splice(fi, 1);
3975 						}
3976 
3977 						for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
3978 							list.callbacks[fi](nodes, list.name, args);
3979 					}
3980 				}
3981 			}
3982 
3983 			return rootNode;
3984 		};
3985 
3986 		// Remove <br> at end of block elements Gecko and WebKit injects BR elements to
3987 		// make it possible to place the caret inside empty blocks. This logic tries to remove
3988 		// these elements and keep br elements that where intended to be there intact
3989 		if (settings.remove_trailing_brs) {
3990 			self.addNodeFilter('br', function(nodes, name) {
3991 				var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()),
3992 					nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
3993 
3994 				// Remove brs from body element as well
3995 				blockElements.body = 1;
3996 
3997 				// Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
3998 				for (i = 0; i < l; i++) {
3999 					node = nodes[i];
4000 					parent = node.parent;
4001 
4002 					if (blockElements[node.parent.name] && node === parent.lastChild) {
4003 						// Loop all nodes to the left of the current node and check for other BR elements
4004 						// excluding bookmarks since they are invisible
4005 						prev = node.prev;
4006 						while (prev) {
4007 							prevName = prev.name;
4008 
4009 							// Ignore bookmarks
4010 							if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
4011 								// Found a non BR element
4012 								if (prevName !== "br")
4013 									break;
4014 	
4015 								// Found another br it's a <br><br> structure then don't remove anything
4016 								if (prevName === 'br') {
4017 									node = null;
4018 									break;
4019 								}
4020 							}
4021 
4022 							prev = prev.prev;
4023 						}
4024 
4025 						if (node) {
4026 							node.remove();
4027 
4028 							// Is the parent to be considered empty after we removed the BR
4029 							if (parent.isEmpty(nonEmptyElements)) {
4030 								elementRule = schema.getElementRule(parent.name);
4031 
4032 								// Remove or padd the element depending on schema rule
4033 								if (elementRule) {
4034 									if (elementRule.removeEmpty)
4035 										parent.remove();
4036 									else if (elementRule.paddEmpty)
4037 										parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
4038 								}
4039 							}
4040 						}
4041 					} else {
4042 						// Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 
4043 						lastParent = node;
4044 						while (parent.firstChild === lastParent && parent.lastChild === lastParent) {
4045 							lastParent = parent;
4046 
4047 							if (blockElements[parent.name]) {
4048 								break;
4049 							}
4050 
4051 							parent = parent.parent;
4052 						}
4053 
4054 						if (lastParent === parent) {
4055 							textNode = new tinymce.html.Node('#text', 3);
4056 							textNode.value = '\u00a0';
4057 							node.replace(textNode);
4058 						}
4059 					}
4060 				}
4061 			});
4062 		}
4063 
4064 		// Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
4065 		if (!settings.allow_html_in_named_anchor) {
4066 			self.addAttributeFilter('id,name', function(nodes, name) {
4067 				var i = nodes.length, sibling, prevSibling, parent, node;
4068 
4069 				while (i--) {
4070 					node = nodes[i];
4071 					if (node.name === 'a' && node.firstChild && !node.attr('href')) {
4072 						parent = node.parent;
4073 
4074 						// Move children after current node
4075 						sibling = node.lastChild;
4076 						do {
4077 							prevSibling = sibling.prev;
4078 							parent.insert(sibling, node);
4079 							sibling = prevSibling;
4080 						} while (sibling);
4081 					}
4082 				}
4083 			});
4084 		}
4085 	}
4086 })(tinymce);
4087 
4088 tinymce.html.Writer = function(settings) {
4089 	var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
4090 
4091 	settings = settings || {};
4092 	indent = settings.indent;
4093 	indentBefore = tinymce.makeMap(settings.indent_before || '');
4094 	indentAfter = tinymce.makeMap(settings.indent_after || '');
4095 	encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
4096 	htmlOutput = settings.element_format == "html";
4097 
4098 	return {
4099 		start: function(name, attrs, empty) {
4100 			var i, l, attr, value;
4101 
4102 			if (indent && indentBefore[name] && html.length > 0) {
4103 				value = html[html.length - 1];
4104 
4105 				if (value.length > 0 && value !== '\n')
4106 					html.push('\n');
4107 			}
4108 
4109 			html.push('<', name);
4110 
4111 			if (attrs) {
4112 				for (i = 0, l = attrs.length; i < l; i++) {
4113 					attr = attrs[i];
4114 					html.push(' ', attr.name, '="', encode(attr.value, true), '"');
4115 				}
4116 			}
4117 
4118 			if (!empty || htmlOutput)
4119 				html[html.length] = '>';
4120 			else
4121 				html[html.length] = ' />';
4122 
4123 			if (empty && indent && indentAfter[name] && html.length > 0) {
4124 				value = html[html.length - 1];
4125 
4126 				if (value.length > 0 && value !== '\n')
4127 					html.push('\n');
4128 			}
4129 		},
4130 
4131 		end: function(name) {
4132 			var value;
4133 
4134 			/*if (indent && indentBefore[name] && html.length > 0) {
4135 				value = html[html.length - 1];
4136 
4137 				if (value.length > 0 && value !== '\n')
4138 					html.push('\n');
4139 			}*/
4140 
4141 			html.push('</', name, '>');
4142 
4143 			if (indent && indentAfter[name] && html.length > 0) {
4144 				value = html[html.length - 1];
4145 
4146 				if (value.length > 0 && value !== '\n')
4147 					html.push('\n');
4148 			}
4149 		},
4150 
4151 		text: function(text, raw) {
4152 			if (text.length > 0)
4153 				html[html.length] = raw ? text : encode(text);
4154 		},
4155 
4156 		cdata: function(text) {
4157 			html.push('<![CDATA[', text, ']]>');
4158 		},
4159 
4160 		comment: function(text) {
4161 			html.push('<!--', text, '-->');
4162 		},
4163 
4164 		pi: function(name, text) {
4165 			if (text)
4166 				html.push('<?', name, ' ', text, '?>');
4167 			else
4168 				html.push('<?', name, '?>');
4169 
4170 			if (indent)
4171 				html.push('\n');
4172 		},
4173 
4174 		doctype: function(text) {
4175 			html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
4176 		},
4177 
4178 		reset: function() {
4179 			html.length = 0;
4180 		},
4181 
4182 		getContent: function() {
4183 			return html.join('').replace(/\n$/, '');
4184 		}
4185 	};
4186 };
4187 
4188 (function(tinymce) {
4189 	tinymce.html.Serializer = function(settings, schema) {
4190 		var self = this, writer = new tinymce.html.Writer(settings);
4191 
4192 		settings = settings || {};
4193 		settings.validate = "validate" in settings ? settings.validate : true;
4194 
4195 		self.schema = schema = schema || new tinymce.html.Schema();
4196 		self.writer = writer;
4197 
4198 		self.serialize = function(node) {
4199 			var handlers, validate;
4200 
4201 			validate = settings.validate;
4202 
4203 			handlers = {
4204 				// #text
4205 				3: function(node, raw) {
4206 					writer.text(node.value, node.raw);
4207 				},
4208 
4209 				// #comment
4210 				8: function(node) {
4211 					writer.comment(node.value);
4212 				},
4213 
4214 				// Processing instruction
4215 				7: function(node) {
4216 					writer.pi(node.name, node.value);
4217 				},
4218 
4219 				// Doctype
4220 				10: function(node) {
4221 					writer.doctype(node.value);
4222 				},
4223 
4224 				// CDATA
4225 				4: function(node) {
4226 					writer.cdata(node.value);
4227 				},
4228 
4229 				// Document fragment
4230 				11: function(node) {
4231 					if ((node = node.firstChild)) {
4232 						do {
4233 							walk(node);
4234 						} while (node = node.next);
4235 					}
4236 				}
4237 			};
4238 
4239 			writer.reset();
4240 
4241 			function walk(node) {
4242 				var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
4243 
4244 				if (!handler) {
4245 					name = node.name;
4246 					isEmpty = node.shortEnded;
4247 					attrs = node.attributes;
4248 
4249 					// Sort attributes
4250 					if (validate && attrs && attrs.length > 1) {
4251 						sortedAttrs = [];
4252 						sortedAttrs.map = {};
4253 
4254 						elementRule = schema.getElementRule(node.name);
4255 						for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
4256 							attrName = elementRule.attributesOrder[i];
4257 
4258 							if (attrName in attrs.map) {
4259 								attrValue = attrs.map[attrName];
4260 								sortedAttrs.map[attrName] = attrValue;
4261 								sortedAttrs.push({name: attrName, value: attrValue});
4262 							}
4263 						}
4264 
4265 						for (i = 0, l = attrs.length; i < l; i++) {
4266 							attrName = attrs[i].name;
4267 
4268 							if (!(attrName in sortedAttrs.map)) {
4269 								attrValue = attrs.map[attrName];
4270 								sortedAttrs.map[attrName] = attrValue;
4271 								sortedAttrs.push({name: attrName, value: attrValue});
4272 							}
4273 						}
4274 
4275 						attrs = sortedAttrs;
4276 					}
4277 
4278 					writer.start(node.name, attrs, isEmpty);
4279 
4280 					if (!isEmpty) {
4281 						if ((node = node.firstChild)) {
4282 							do {
4283 								walk(node);
4284 							} while (node = node.next);
4285 						}
4286 
4287 						writer.end(name);
4288 					}
4289 				} else
4290 					handler(node);
4291 			}
4292 
4293 			// Serialize element and treat all non elements as fragments
4294 			if (node.type == 1 && !settings.inner)
4295 				walk(node);
4296 			else
4297 				handlers[11](node);
4298 
4299 			return writer.getContent();
4300 		};
4301 	}
4302 })(tinymce);
4303 
4304 // JSLint defined globals
4305 /*global tinymce:false, window:false */
4306 
4307 tinymce.dom = {};
4308 
4309 (function(namespace, expando) {
4310 	var w3cEventModel = !!document.addEventListener;
4311 
4312 	function addEvent(target, name, callback, capture) {
4313 		if (target.addEventListener) {
4314 			target.addEventListener(name, callback, capture || false);
4315 		} else if (target.attachEvent) {
4316 			target.attachEvent('on' + name, callback);
4317 		}
4318 	}
4319 
4320 	function removeEvent(target, name, callback, capture) {
4321 		if (target.removeEventListener) {
4322 			target.removeEventListener(name, callback, capture || false);
4323 		} else if (target.detachEvent) {
4324 			target.detachEvent('on' + name, callback);
4325 		}
4326 	}
4327 
4328 	function fix(original_event, data) {
4329 		var name, event = data || {};
4330 
4331 		// Dummy function that gets replaced on the delegation state functions
4332 		function returnFalse() {
4333 			return false;
4334 		}
4335 
4336 		// Dummy function that gets replaced on the delegation state functions
4337 		function returnTrue() {
4338 			return true;
4339 		}
4340 
4341 		// Copy all properties from the original event
4342 		for (name in original_event) {
4343 			// layerX/layerY is deprecated in Chrome and produces a warning
4344 			if (name !== "layerX" && name !== "layerY") {
4345 				event[name] = original_event[name];
4346 			}
4347 		}
4348 
4349 		// Normalize target IE uses srcElement
4350 		if (!event.target) {
4351 			event.target = event.srcElement || document;
4352 		}
4353 
4354 		// Add preventDefault method
4355 		event.preventDefault = function() {
4356 			event.isDefaultPrevented = returnTrue;
4357 
4358 			// Execute preventDefault on the original event object
4359 			if (original_event) {
4360 				if (original_event.preventDefault) {
4361 					original_event.preventDefault();
4362 				} else {
4363 					original_event.returnValue = false; // IE
4364 				}
4365 			}
4366 		};
4367 
4368 		// Add stopPropagation
4369 		event.stopPropagation = function() {
4370 			event.isPropagationStopped = returnTrue;
4371 
4372 			// Execute stopPropagation on the original event object
4373 			if (original_event) {
4374 				if (original_event.stopPropagation) {
4375 					original_event.stopPropagation();
4376 				} else {
4377 					original_event.cancelBubble = true; // IE
4378 				}
4379 			 }
4380 		};
4381 
4382 		// Add stopImmediatePropagation
4383 		event.stopImmediatePropagation = function() {
4384 			event.isImmediatePropagationStopped = returnTrue;
4385 			event.stopPropagation();
4386 		};
4387 
4388 		// Add event delegation states
4389 		if (!event.isDefaultPrevented) {
4390 			event.isDefaultPrevented = returnFalse;
4391 			event.isPropagationStopped = returnFalse;
4392 			event.isImmediatePropagationStopped = returnFalse;
4393 		}
4394 
4395 		return event;
4396 	}
4397 
4398 	function bindOnReady(win, callback, event_utils) {
4399 		var doc = win.document, event = {type: 'ready'};
4400 
4401 		// Gets called when the DOM is ready
4402 		function readyHandler() {
4403 			if (!event_utils.domLoaded) {
4404 				event_utils.domLoaded = true;
4405 				callback(event);
4406 			}
4407 		}
4408 
4409 		// Use W3C method
4410 		if (w3cEventModel) {
4411 			addEvent(win, 'DOMContentLoaded', readyHandler);
4412 		} else {
4413 			// Use IE method
4414 			addEvent(doc, "readystatechange", function() {
4415 				if (doc.readyState === "complete") {
4416 					removeEvent(doc, "readystatechange", arguments.callee);
4417 					readyHandler();
4418 				}
4419 			});
4420 
4421 			// Wait until we can scroll, when we can the DOM is initialized
4422 			if (doc.documentElement.doScroll && win === win.top) {
4423 				(function() {
4424 					try {
4425 						// If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
4426 						// http://javascript.nwbox.com/IEContentLoaded/
4427 						doc.documentElement.doScroll("left");
4428 					} catch (ex) {
4429 						setTimeout(arguments.callee, 0);
4430 						return;
4431 					}
4432 
4433 					readyHandler();
4434 				})();
4435 			}
4436 		}
4437 
4438 		// Fallback if any of the above methods should fail for some odd reason
4439 		addEvent(win, 'load', readyHandler);
4440 	}
4441 
4442 	function EventUtils(proxy) {
4443 		var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
4444 
4445 		hasMouseEnterLeave = "onmouseenter" in document.documentElement;
4446 		hasFocusIn = "onfocusin" in document.documentElement;
4447 		mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
4448 		count = 1;
4449 
4450 		// State if the DOMContentLoaded was executed or not
4451 		self.domLoaded = false;
4452 		self.events = events;
4453 
4454 		function executeHandlers(evt, id) {
4455 			var callbackList, i, l, callback;
4456 
4457 			callbackList = events[id][evt.type];
4458 			if (callbackList) {
4459 				for (i = 0, l = callbackList.length; i < l; i++) {
4460 					callback = callbackList[i];
4461 					
4462 					// Check if callback exists might be removed if a unbind is called inside the callback
4463 					if (callback && callback.func.call(callback.scope, evt) === false) {
4464 						evt.preventDefault();
4465 					}
4466 
4467 					// Should we stop propagation to immediate listeners
4468 					if (evt.isImmediatePropagationStopped()) {
4469 						return;
4470 					}
4471 				}
4472 			}
4473 		}
4474 
4475 		self.bind = function(target, names, callback, scope) {
4476 			var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
4477 
4478 			// Native event handler function patches the event and executes the callbacks for the expando
4479 			function defaultNativeHandler(evt) {
4480 				executeHandlers(fix(evt || win.event), id);
4481 			}
4482 
4483 			// Don't bind to text nodes or comments
4484 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4485 				return;
4486 			}
4487 
4488 			// Create or get events id for the target
4489 			if (!target[expando]) {
4490 				id = count++;
4491 				target[expando] = id;
4492 				events[id] = {};
4493 			} else {
4494 				id = target[expando];
4495 
4496 				if (!events[id]) {
4497 					events[id] = {};
4498 				}
4499 			}
4500 
4501 			// Setup the specified scope or use the target as a default
4502 			scope = scope || target;
4503 
4504 			// Split names and bind each event, enables you to bind multiple events with one call
4505 			names = names.split(' ');
4506 			i = names.length;
4507 			while (i--) {
4508 				name = names[i];
4509 				nativeHandler = defaultNativeHandler;
4510 				fakeName = capture = false;
4511 
4512 				// Use ready instead of DOMContentLoaded
4513 				if (name === "DOMContentLoaded") {
4514 					name = "ready";
4515 				}
4516 
4517 				// DOM is already ready
4518 				if ((self.domLoaded || target.readyState == 'complete') && name === "ready") {
4519 					self.domLoaded = true;
4520 					callback.call(scope, fix({type: name}));
4521 					continue;
4522 				}
4523 
4524 				// Handle mouseenter/mouseleaver
4525 				if (!hasMouseEnterLeave) {
4526 					fakeName = mouseEnterLeave[name];
4527 
4528 					if (fakeName) {
4529 						nativeHandler = function(evt) {
4530 							var current, related;
4531 
4532 							current = evt.currentTarget;
4533 							related = evt.relatedTarget;
4534 
4535 							// Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element
4536 							if (related && current.contains) {
4537 								// Use contains for performance
4538 								related = current.contains(related);
4539 							} else {
4540 								while (related && related !== current) {
4541 									related = related.parentNode;
4542 								}
4543 							}
4544 
4545 							// Fire fake event
4546 							if (!related) {
4547 								evt = fix(evt || win.event);
4548 								evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
4549 								evt.target = current;
4550 								executeHandlers(evt, id);
4551 							}
4552 						};
4553 					}
4554 				}
4555 
4556 				// Fake bubbeling of focusin/focusout
4557 				if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
4558 					capture = true;
4559 					fakeName = name === "focusin" ? "focus" : "blur";
4560 					nativeHandler = function(evt) {
4561 						evt = fix(evt || win.event);
4562 						evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
4563 						executeHandlers(evt, id);
4564 					};
4565 				}
4566 
4567 				// Setup callback list and bind native event
4568 				callbackList = events[id][name];
4569 				if (!callbackList) {
4570 					events[id][name] = callbackList = [{func: callback, scope: scope}];
4571 					callbackList.fakeName = fakeName;
4572 					callbackList.capture = capture;
4573 
4574 					// Add the nativeHandler to the callback list so that we can later unbind it
4575 					callbackList.nativeHandler = nativeHandler;
4576 					if (!w3cEventModel) {
4577 						callbackList.proxyHandler = proxy(id);
4578 					}
4579 
4580 					// Check if the target has native events support
4581 					if (name === "ready") {
4582 						bindOnReady(target, nativeHandler, self);
4583 					} else {
4584 						addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture);
4585 					}
4586 				} else {
4587 					// If it already has an native handler then just push the callback
4588 					callbackList.push({func: callback, scope: scope});
4589 				}
4590 			}
4591 
4592 			target = callbackList = 0; // Clean memory for IE
4593 
4594 			return callback;
4595 		};
4596 
4597 		self.unbind = function(target, names, callback) {
4598 			var id, callbackList, i, ci, name, eventMap;
4599 
4600 			// Don't bind to text nodes or comments
4601 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4602 				return self;
4603 			}
4604 
4605 			// Unbind event or events if the target has the expando
4606 			id = target[expando];
4607 			if (id) {
4608 				eventMap = events[id];
4609 
4610 				// Specific callback
4611 				if (names) {
4612 					names = names.split(' ');
4613 					i = names.length;
4614 					while (i--) {
4615 						name = names[i];
4616 						callbackList = eventMap[name];
4617 
4618 						// Unbind the event if it exists in the map
4619 						if (callbackList) {
4620 							// Remove specified callback
4621 							if (callback) {
4622 								ci = callbackList.length;
4623 								while (ci--) {
4624 									if (callbackList[ci].func === callback) {
4625 										callbackList.splice(ci, 1);
4626 									}
4627 								}
4628 							}
4629 
4630 							// Remove all callbacks if there isn't a specified callback or there is no callbacks left
4631 							if (!callback || callbackList.length === 0) {
4632 								delete eventMap[name];
4633 								removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4634 							}
4635 						}
4636 					}
4637 				} else {
4638 					// All events for a specific element
4639 					for (name in eventMap) {
4640 						callbackList = eventMap[name];
4641 						removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4642 					}
4643 
4644 					eventMap = {};
4645 				}
4646 
4647 				// Check if object is empty, if it isn't then we won't remove the expando map
4648 				for (name in eventMap) {
4649 					return self;
4650 				}
4651 
4652 				// Delete event object
4653 				delete events[id];
4654 
4655 				// Remove expando from target
4656 				try {
4657 					// IE will fail here since it can't delete properties from window
4658 					delete target[expando];
4659 				} catch (ex) {
4660 					// IE will set it to null
4661 					target[expando] = null;
4662 				}
4663 			}
4664 
4665 			return self;
4666 		};
4667 
4668 		self.fire = function(target, name, args) {
4669 			var id, event;
4670 
4671 			// Don't bind to text nodes or comments
4672 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4673 				return self;
4674 			}
4675 
4676 			// Build event object by patching the args
4677 			event = fix(null, args);
4678 			event.type = name;
4679 
4680 			do {
4681 				// Found an expando that means there is listeners to execute
4682 				id = target[expando];
4683 				if (id) {
4684 					executeHandlers(event, id);
4685 				}
4686 
4687 				// Walk up the DOM
4688 				target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
4689 			} while (target && !event.isPropagationStopped());
4690 
4691 			return self;
4692 		};
4693 
4694 		self.clean = function(target) {
4695 			var i, children, unbind = self.unbind;
4696 	
4697 			// Don't bind to text nodes or comments
4698 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4699 				return self;
4700 			}
4701 
4702 			// Unbind any element on the specificed target
4703 			if (target[expando]) {
4704 				unbind(target);
4705 			}
4706 
4707 			// Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
4708 			if (!target.getElementsByTagName) {
4709 				target = target.document;
4710 			}
4711 
4712 			// Remove events from each child element
4713 			if (target && target.getElementsByTagName) {
4714 				unbind(target);
4715 
4716 				children = target.getElementsByTagName('*');
4717 				i = children.length;
4718 				while (i--) {
4719 					target = children[i];
4720 
4721 					if (target[expando]) {
4722 						unbind(target);
4723 					}
4724 				}
4725 			}
4726 
4727 			return self;
4728 		};
4729 
4730 		self.callNativeHandler = function(id, evt) {
4731 			if (events) {
4732 				events[id][evt.type].nativeHandler(evt);
4733 			}
4734 		};
4735 
4736 		self.destory = function() {
4737 			events = {};
4738 		};
4739 
4740 		// Legacy function calls
4741 
4742 		self.add = function(target, events, func, scope) {
4743 			// Old API supported direct ID assignment
4744 			if (typeof(target) === "string") {
4745 				target = document.getElementById(target);
4746 			}
4747 
4748 			// Old API supported multiple targets
4749 			if (target && target instanceof Array) {
4750 				var i = target.length;
4751 
4752 				while (i--) {
4753 					self.add(target[i], events, func, scope);
4754 				}
4755 
4756 				return;
4757 			}
4758 
4759 			// Old API called ready init
4760 			if (events === "init") {
4761 				events = "ready";
4762 			}
4763 
4764 			return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope);
4765 		};
4766 
4767 		self.remove = function(target, events, func, scope) {
4768 			if (!target) {
4769 				return self;
4770 			}
4771 
4772 			// Old API supported direct ID assignment
4773 			if (typeof(target) === "string") {
4774 				target = document.getElementById(target);
4775 			}
4776 
4777 			// Old API supported multiple targets
4778 			if (target instanceof Array) {
4779 				var i = target.length;
4780 
4781 				while (i--) {
4782 					self.remove(target[i], events, func, scope);
4783 				}
4784 
4785 				return self;
4786 			}
4787 
4788 			return self.unbind(target, events instanceof Array ? events.join(' ') : events, func);
4789 		};
4790 
4791 		self.clear = function(target) {
4792 			// Old API supported direct ID assignment
4793 			if (typeof(target) === "string") {
4794 				target = document.getElementById(target);
4795 			}
4796 
4797 			return self.clean(target);
4798 		};
4799 
4800 		self.cancel = function(e) {
4801 			if (e) {
4802 				self.prevent(e);
4803 				self.stop(e);
4804 			}
4805 
4806 			return false;
4807 		};
4808 
4809 		self.prevent = function(e) {
4810 			if (!e.preventDefault) {
4811 				e = fix(e);
4812 			}
4813 
4814 			e.preventDefault();
4815 
4816 			return false;
4817 		};
4818 
4819 		self.stop = function(e) {
4820 			if (!e.stopPropagation) {
4821 				e = fix(e);
4822 			}
4823 
4824 			e.stopPropagation();
4825 
4826 			return false;
4827 		};
4828 	}
4829 
4830 	namespace.EventUtils = EventUtils;
4831 
4832 	namespace.Event = new EventUtils(function(id) {
4833 		return function(evt) {
4834 			tinymce.dom.Event.callNativeHandler(id, evt);
4835 		};
4836 	});
4837 
4838 	// Bind ready event when tinymce script is loaded
4839 	namespace.Event.bind(window, 'ready', function() {});
4840 
4841 	namespace = 0;
4842 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando
4843 
4844 tinymce.dom.TreeWalker = function(start_node, root_node) {
4845 	var node = start_node;
4846 
4847 	function findSibling(node, start_name, sibling_name, shallow) {
4848 		var sibling, parent;
4849 
4850 		if (node) {
4851 			// Walk into nodes if it has a start
4852 			if (!shallow && node[start_name])
4853 				return node[start_name];
4854 
4855 			// Return the sibling if it has one
4856 			if (node != root_node) {
4857 				sibling = node[sibling_name];
4858 				if (sibling)
4859 					return sibling;
4860 
4861 				// Walk up the parents to look for siblings
4862 				for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
4863 					sibling = parent[sibling_name];
4864 					if (sibling)
4865 						return sibling;
4866 				}
4867 			}
4868 		}
4869 	};
4870 
4871 	this.current = function() {
4872 		return node;
4873 	};
4874 
4875 	this.next = function(shallow) {
4876 		return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
4877 	};
4878 
4879 	this.prev = function(shallow) {
4880 		return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
4881 	};
4882 };
4883 
4884 (function(tinymce) {
4885 	// Shorten names
4886 	var each = tinymce.each,
4887 		is = tinymce.is,
4888 		isWebKit = tinymce.isWebKit,
4889 		isIE = tinymce.isIE,
4890 		Entities = tinymce.html.Entities,
4891 		simpleSelectorRe = /^([a-z0-9],?)+$/i,
4892 		whiteSpaceRegExp = /^[ \t\r\n]*$/;
4893 
4894 	tinymce.create('tinymce.dom.DOMUtils', {
4895 		doc : null,
4896 		root : null,
4897 		files : null,
4898 		pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
4899 		props : {
4900 			"for" : "htmlFor",
4901 			"class" : "className",
4902 			className : "className",
4903 			checked : "checked",
4904 			disabled : "disabled",
4905 			maxlength : "maxLength",
4906 			readonly : "readOnly",
4907 			selected : "selected",
4908 			value : "value",
4909 			id : "id",
4910 			name : "name",
4911 			type : "type"
4912 		},
4913 
4914 		DOMUtils : function(d, s) {
4915 			var t = this, globalStyle, name, blockElementsMap;
4916 
4917 			t.doc = d;
4918 			t.win = window;
4919 			t.files = {};
4920 			t.cssFlicker = false;
4921 			t.counter = 0;
4922 			t.stdMode = !tinymce.isIE || d.documentMode >= 8;
4923 			t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
4924 			t.hasOuterHTML = "outerHTML" in d.createElement("a");
4925 
4926 			t.settings = s = tinymce.extend({
4927 				keep_values : false,
4928 				hex_colors : 1
4929 			}, s);
4930 			
4931 			t.schema = s.schema;
4932 			t.styles = new tinymce.html.Styles({
4933 				url_converter : s.url_converter,
4934 				url_converter_scope : s.url_converter_scope
4935 			}, s.schema);
4936 
4937 			// Fix IE6SP2 flicker and check it failed for pre SP2
4938 			if (tinymce.isIE6) {
4939 				try {
4940 					d.execCommand('BackgroundImageCache', false, true);
4941 				} catch (e) {
4942 					t.cssFlicker = true;
4943 				}
4944 			}
4945 
4946 			t.fixDoc(d);
4947 			t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event;
4948 			tinymce.addUnload(t.destroy, t);
4949 			blockElementsMap = s.schema ? s.schema.getBlockElements() : {};
4950 
4951 			t.isBlock = function(node) {
4952 				// This function is called in module pattern style since it might be executed with the wrong this scope
4953 				var type = node.nodeType;
4954 
4955 				// If it's a node then check the type and use the nodeName
4956 				if (type)
4957 					return !!(type === 1 && blockElementsMap[node.nodeName]);
4958 
4959 				return !!blockElementsMap[node];
4960 			};
4961 		},
4962 
4963 		fixDoc: function(doc) {
4964 			var settings = this.settings, name;
4965 
4966 			if (isIE && settings.schema) {
4967 				// Add missing HTML 4/5 elements to IE
4968 				('abbr article aside audio canvas ' +
4969 				'details figcaption figure footer ' +
4970 				'header hgroup mark menu meter nav ' +
4971 				'output progress section summary ' +
4972 				'time video').replace(/\w+/g, function(name) {
4973 					doc.createElement(name);
4974 				});
4975 
4976 				// Create all custom elements
4977 				for (name in settings.schema.getCustomElements()) {
4978 					doc.createElement(name);
4979 				}
4980 			}
4981 		},
4982 
4983 		clone: function(node, deep) {
4984 			var self = this, clone, doc;
4985 
4986 			// TODO: Add feature detection here in the future
4987 			if (!isIE || node.nodeType !== 1 || deep) {
4988 				return node.cloneNode(deep);
4989 			}
4990 
4991 			doc = self.doc;
4992 
4993 			// Make a HTML5 safe shallow copy
4994 			if (!deep) {
4995 				clone = doc.createElement(node.nodeName);
4996 
4997 				// Copy attribs
4998 				each(self.getAttribs(node), function(attr) {
4999 					self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
5000 				});
5001 
5002 				return clone;
5003 			}
5004 /*
5005 			// Setup HTML5 patched document fragment
5006 			if (!self.frag) {
5007 				self.frag = doc.createDocumentFragment();
5008 				self.fixDoc(self.frag);
5009 			}
5010 
5011 			// Make a deep copy by adding it to the document fragment then removing it this removed the :section
5012 			clone = doc.createElement('div');
5013 			self.frag.appendChild(clone);
5014 			clone.innerHTML = node.outerHTML;
5015 			self.frag.removeChild(clone);
5016 */
5017 			return clone.firstChild;
5018 		},
5019 
5020 		getRoot : function() {
5021 			var t = this, s = t.settings;
5022 
5023 			return (s && t.get(s.root_element)) || t.doc.body;
5024 		},
5025 
5026 		getViewPort : function(w) {
5027 			var d, b;
5028 
5029 			w = !w ? this.win : w;
5030 			d = w.document;
5031 			b = this.boxModel ? d.documentElement : d.body;
5032 
5033 			// Returns viewport size excluding scrollbars
5034 			return {
5035 				x : w.pageXOffset || b.scrollLeft,
5036 				y : w.pageYOffset || b.scrollTop,
5037 				w : w.innerWidth || b.clientWidth,
5038 				h : w.innerHeight || b.clientHeight
5039 			};
5040 		},
5041 
5042 		getRect : function(e) {
5043 			var p, t = this, sr;
5044 
5045 			e = t.get(e);
5046 			p = t.getPos(e);
5047 			sr = t.getSize(e);
5048 
5049 			return {
5050 				x : p.x,
5051 				y : p.y,
5052 				w : sr.w,
5053 				h : sr.h
5054 			};
5055 		},
5056 
5057 		getSize : function(e) {
5058 			var t = this, w, h;
5059 
5060 			e = t.get(e);
5061 			w = t.getStyle(e, 'width');
5062 			h = t.getStyle(e, 'height');
5063 
5064 			// Non pixel value, then force offset/clientWidth
5065 			if (w.indexOf('px') === -1)
5066 				w = 0;
5067 
5068 			// Non pixel value, then force offset/clientWidth
5069 			if (h.indexOf('px') === -1)
5070 				h = 0;
5071 
5072 			return {
5073 				w : parseInt(w, 10) || e.offsetWidth || e.clientWidth,
5074 				h : parseInt(h, 10) || e.offsetHeight || e.clientHeight
5075 			};
5076 		},
5077 
5078 		getParent : function(n, f, r) {
5079 			return this.getParents(n, f, r, false);
5080 		},
5081 
5082 		getParents : function(n, f, r, c) {
5083 			var t = this, na, se = t.settings, o = [];
5084 
5085 			n = t.get(n);
5086 			c = c === undefined;
5087 
5088 			if (se.strict_root)
5089 				r = r || t.getRoot();
5090 
5091 			// Wrap node name as func
5092 			if (is(f, 'string')) {
5093 				na = f;
5094 
5095 				if (f === '*') {
5096 					f = function(n) {return n.nodeType == 1;};
5097 				} else {
5098 					f = function(n) {
5099 						return t.is(n, na);
5100 					};
5101 				}
5102 			}
5103 
5104 			while (n) {
5105 				if (n == r || !n.nodeType || n.nodeType === 9)
5106 					break;
5107 
5108 				if (!f || f(n)) {
5109 					if (c)
5110 						o.push(n);
5111 					else
5112 						return n;
5113 				}
5114 
5115 				n = n.parentNode;
5116 			}
5117 
5118 			return c ? o : null;
5119 		},
5120 
5121 		get : function(e) {
5122 			var n;
5123 
5124 			if (e && this.doc && typeof(e) == 'string') {
5125 				n = e;
5126 				e = this.doc.getElementById(e);
5127 
5128 				// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
5129 				if (e && e.id !== n)
5130 					return this.doc.getElementsByName(n)[1];
5131 			}
5132 
5133 			return e;
5134 		},
5135 
5136 		getNext : function(node, selector) {
5137 			return this._findSib(node, selector, 'nextSibling');
5138 		},
5139 
5140 		getPrev : function(node, selector) {
5141 			return this._findSib(node, selector, 'previousSibling');
5142 		},
5143 
5144 
5145 		select : function(pa, s) {
5146 			var t = this;
5147 
5148 			return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
5149 		},
5150 
5151 		is : function(n, selector) {
5152 			var i;
5153 
5154 			// If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
5155 			if (n.length === undefined) {
5156 				// Simple all selector
5157 				if (selector === '*')
5158 					return n.nodeType == 1;
5159 
5160 				// Simple selector just elements
5161 				if (simpleSelectorRe.test(selector)) {
5162 					selector = selector.toLowerCase().split(/,/);
5163 					n = n.nodeName.toLowerCase();
5164 
5165 					for (i = selector.length - 1; i >= 0; i--) {
5166 						if (selector[i] == n)
5167 							return true;
5168 					}
5169 
5170 					return false;
5171 				}
5172 			}
5173 
5174 			return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
5175 		},
5176 
5177 
5178 		add : function(p, n, a, h, c) {
5179 			var t = this;
5180 
5181 			return this.run(p, function(p) {
5182 				var e, k;
5183 
5184 				e = is(n, 'string') ? t.doc.createElement(n) : n;
5185 				t.setAttribs(e, a);
5186 
5187 				if (h) {
5188 					if (h.nodeType)
5189 						e.appendChild(h);
5190 					else
5191 						t.setHTML(e, h);
5192 				}
5193 
5194 				return !c ? p.appendChild(e) : e;
5195 			});
5196 		},
5197 
5198 		create : function(n, a, h) {
5199 			return this.add(this.doc.createElement(n), n, a, h, 1);
5200 		},
5201 
5202 		createHTML : function(n, a, h) {
5203 			var o = '', t = this, k;
5204 
5205 			o += '<' + n;
5206 
5207 			for (k in a) {
5208 				if (a.hasOwnProperty(k))
5209 					o += ' ' + k + '="' + t.encode(a[k]) + '"';
5210 			}
5211 
5212 			// A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
5213 			if (typeof(h) != "undefined")
5214 				return o + '>' + h + '</' + n + '>';
5215 
5216 			return o + ' />';
5217 		},
5218 
5219 		remove : function(node, keep_children) {
5220 			return this.run(node, function(node) {
5221 				var child, parent = node.parentNode;
5222 
5223 				if (!parent)
5224 					return null;
5225 
5226 				if (keep_children) {
5227 					while (child = node.firstChild) {
5228 						// IE 8 will crash if you don't remove completely empty text nodes
5229 						if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
5230 							parent.insertBefore(child, node);
5231 						else
5232 							node.removeChild(child);
5233 					}
5234 				}
5235 
5236 				return parent.removeChild(node);
5237 			});
5238 		},
5239 
5240 		setStyle : function(n, na, v) {
5241 			var t = this;
5242 
5243 			return t.run(n, function(e) {
5244 				var s, i;
5245 
5246 				s = e.style;
5247 
5248 				// Camelcase it, if needed
5249 				na = na.replace(/-(\D)/g, function(a, b){
5250 					return b.toUpperCase();
5251 				});
5252 
5253 				// Default px suffix on these
5254 				if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
5255 					v += 'px';
5256 
5257 				switch (na) {
5258 					case 'opacity':
5259 						// IE specific opacity
5260 						if (isIE) {
5261 							s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
5262 
5263 							if (!n.currentStyle || !n.currentStyle.hasLayout)
5264 								s.display = 'inline-block';
5265 						}
5266 
5267 						// Fix for older browsers
5268 						s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
5269 						break;
5270 
5271 					case 'float':
5272 						isIE ? s.styleFloat = v : s.cssFloat = v;
5273 						break;
5274 					
5275 					default:
5276 						s[na] = v || '';
5277 				}
5278 
5279 				// Force update of the style data
5280 				if (t.settings.update_styles)
5281 					t.setAttrib(e, 'data-mce-style');
5282 			});
5283 		},
5284 
5285 		getStyle : function(n, na, c) {
5286 			n = this.get(n);
5287 
5288 			if (!n)
5289 				return;
5290 
5291 			// Gecko
5292 			if (this.doc.defaultView && c) {
5293 				// Remove camelcase
5294 				na = na.replace(/[A-Z]/g, function(a){
5295 					return '-' + a;
5296 				});
5297 
5298 				try {
5299 					return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
5300 				} catch (ex) {
5301 					// Old safari might fail
5302 					return null;
5303 				}
5304 			}
5305 
5306 			// Camelcase it, if needed
5307 			na = na.replace(/-(\D)/g, function(a, b){
5308 				return b.toUpperCase();
5309 			});
5310 
5311 			if (na == 'float')
5312 				na = isIE ? 'styleFloat' : 'cssFloat';
5313 
5314 			// IE & Opera
5315 			if (n.currentStyle && c)
5316 				return n.currentStyle[na];
5317 
5318 			return n.style ? n.style[na] : undefined;
5319 		},
5320 
5321 		setStyles : function(e, o) {
5322 			var t = this, s = t.settings, ol;
5323 
5324 			ol = s.update_styles;
5325 			s.update_styles = 0;
5326 
5327 			each(o, function(v, n) {
5328 				t.setStyle(e, n, v);
5329 			});
5330 
5331 			// Update style info
5332 			s.update_styles = ol;
5333 			if (s.update_styles)
5334 				t.setAttrib(e, s.cssText);
5335 		},
5336 
5337 		removeAllAttribs: function(e) {
5338 			return this.run(e, function(e) {
5339 				var i, attrs = e.attributes;
5340 				for (i = attrs.length - 1; i >= 0; i--) {
5341 					e.removeAttributeNode(attrs.item(i));
5342 				}
5343 			});
5344 		},
5345 
5346 		setAttrib : function(e, n, v) {
5347 			var t = this;
5348 
5349 			// Whats the point
5350 			if (!e || !n)
5351 				return;
5352 
5353 			// Strict XML mode
5354 			if (t.settings.strict)
5355 				n = n.toLowerCase();
5356 
5357 			return this.run(e, function(e) {
5358 				var s = t.settings;
5359 				var originalValue = e.getAttribute(n);
5360 				if (v !== null) {
5361 					switch (n) {
5362 						case "style":
5363 							if (!is(v, 'string')) {
5364 								each(v, function(v, n) {
5365 									t.setStyle(e, n, v);
5366 								});
5367 
5368 								return;
5369 							}
5370 
5371 							// No mce_style for elements with these since they might get resized by the user
5372 							if (s.keep_values) {
5373 								if (v && !t._isRes(v))
5374 									e.setAttribute('data-mce-style', v, 2);
5375 								else
5376 									e.removeAttribute('data-mce-style', 2);
5377 							}
5378 
5379 							e.style.cssText = v;
5380 							break;
5381 
5382 						case "class":
5383 							e.className = v || ''; // Fix IE null bug
5384 							break;
5385 
5386 						case "src":
5387 						case "href":
5388 							if (s.keep_values) {
5389 								if (s.url_converter)
5390 									v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
5391 
5392 								t.setAttrib(e, 'data-mce-' + n, v, 2);
5393 							}
5394 
5395 							break;
5396 
5397 						case "shape":
5398 							e.setAttribute('data-mce-style', v);
5399 							break;
5400 					}
5401 				}
5402 				if (is(v) && v !== null && v.length !== 0)
5403 					e.setAttribute(n, '' + v, 2);
5404 				else
5405 					e.removeAttribute(n, 2);
5406 
5407 				// fire onChangeAttrib event for attributes that have changed
5408 				if (tinyMCE.activeEditor && originalValue != v) {
5409 					var ed = tinyMCE.activeEditor;
5410 					ed.onSetAttrib.dispatch(ed, e, n, v);
5411 				}
5412 			});
5413 		},
5414 
5415 		setAttribs : function(e, o) {
5416 			var t = this;
5417 
5418 			return this.run(e, function(e) {
5419 				each(o, function(v, n) {
5420 					t.setAttrib(e, n, v);
5421 				});
5422 			});
5423 		},
5424 
5425 		getAttrib : function(e, n, dv) {
5426 			var v, t = this, undef;
5427 
5428 			e = t.get(e);
5429 
5430 			if (!e || e.nodeType !== 1)
5431 				return dv === undef ? false : dv;
5432 
5433 			if (!is(dv))
5434 				dv = '';
5435 
5436 			// Try the mce variant for these
5437 			if (/^(src|href|style|coords|shape)$/.test(n)) {
5438 				v = e.getAttribute("data-mce-" + n);
5439 
5440 				if (v)
5441 					return v;
5442 			}
5443 
5444 			if (isIE && t.props[n]) {
5445 				v = e[t.props[n]];
5446 				v = v && v.nodeValue ? v.nodeValue : v;
5447 			}
5448 
5449 			if (!v)
5450 				v = e.getAttribute(n, 2);
5451 
5452 			// Check boolean attribs
5453 			if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
5454 				if (e[t.props[n]] === true && v === '')
5455 					return n;
5456 
5457 				return v ? n : '';
5458 			}
5459 
5460 			// Inner input elements will override attributes on form elements
5461 			if (e.nodeName === "FORM" && e.getAttributeNode(n))
5462 				return e.getAttributeNode(n).nodeValue;
5463 
5464 			if (n === 'style') {
5465 				v = v || e.style.cssText;
5466 
5467 				if (v) {
5468 					v = t.serializeStyle(t.parseStyle(v), e.nodeName);
5469 
5470 					if (t.settings.keep_values && !t._isRes(v))
5471 						e.setAttribute('data-mce-style', v);
5472 				}
5473 			}
5474 
5475 			// Remove Apple and WebKit stuff
5476 			if (isWebKit && n === "class" && v)
5477 				v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
5478 
5479 			// Handle IE issues
5480 			if (isIE) {
5481 				switch (n) {
5482 					case 'rowspan':
5483 					case 'colspan':
5484 						// IE returns 1 as default value
5485 						if (v === 1)
5486 							v = '';
5487 
5488 						break;
5489 
5490 					case 'size':
5491 						// IE returns +0 as default value for size
5492 						if (v === '+0' || v === 20 || v === 0)
5493 							v = '';
5494 
5495 						break;
5496 
5497 					case 'width':
5498 					case 'height':
5499 					case 'vspace':
5500 					case 'checked':
5501 					case 'disabled':
5502 					case 'readonly':
5503 						if (v === 0)
5504 							v = '';
5505 
5506 						break;
5507 
5508 					case 'hspace':
5509 						// IE returns -1 as default value
5510 						if (v === -1)
5511 							v = '';
5512 
5513 						break;
5514 
5515 					case 'maxlength':
5516 					case 'tabindex':
5517 						// IE returns default value
5518 						if (v === 32768 || v === 2147483647 || v === '32768')
5519 							v = '';
5520 
5521 						break;
5522 
5523 					case 'multiple':
5524 					case 'compact':
5525 					case 'noshade':
5526 					case 'nowrap':
5527 						if (v === 65535)
5528 							return n;
5529 
5530 						return dv;
5531 
5532 					case 'shape':
5533 						v = v.toLowerCase();
5534 						break;
5535 
5536 					default:
5537 						// IE has odd anonymous function for event attributes
5538 						if (n.indexOf('on') === 0 && v)
5539 							v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
5540 				}
5541 			}
5542 
5543 			return (v !== undef && v !== null && v !== '') ? '' + v : dv;
5544 		},
5545 
5546 		getPos : function(n, ro) {
5547 			var t = this, x = 0, y = 0, e, d = t.doc, r;
5548 
5549 			n = t.get(n);
5550 			ro = ro || d.body;
5551 
5552 			if (n) {
5553 				// Use getBoundingClientRect if it exists since it's faster than looping offset nodes
5554 				if (n.getBoundingClientRect) {
5555 					n = n.getBoundingClientRect();
5556 					e = t.boxModel ? d.documentElement : d.body;
5557 
5558 					// Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
5559 					// Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
5560 					x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
5561 					y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
5562 
5563 					return {x : x, y : y};
5564 				}
5565 
5566 				r = n;
5567 				while (r && r != ro && r.nodeType) {
5568 					x += r.offsetLeft || 0;
5569 					y += r.offsetTop || 0;
5570 					r = r.offsetParent;
5571 				}
5572 
5573 				r = n.parentNode;
5574 				while (r && r != ro && r.nodeType) {
5575 					x -= r.scrollLeft || 0;
5576 					y -= r.scrollTop || 0;
5577 					r = r.parentNode;
5578 				}
5579 			}
5580 
5581 			return {x : x, y : y};
5582 		},
5583 
5584 		parseStyle : function(st) {
5585 			return this.styles.parse(st);
5586 		},
5587 
5588 		serializeStyle : function(o, name) {
5589 			return this.styles.serialize(o, name);
5590 		},
5591 
5592 		addStyle: function(cssText) {
5593 			var doc = this.doc, head;
5594 
5595 			// Create style element if needed
5596 			styleElm = doc.getElementById('mceDefaultStyles');
5597 			if (!styleElm) {
5598 				styleElm = doc.createElement('style'),
5599 				styleElm.id = 'mceDefaultStyles';
5600 				styleElm.type = 'text/css';
5601 
5602 				head = doc.getElementsByTagName('head')[0]
5603 				if (head.firstChild) {
5604 					head.insertBefore(styleElm, head.firstChild);
5605 				} else {
5606 					head.appendChild(styleElm);
5607 				}
5608 			}
5609 
5610 			// Append style data to old or new style element
5611 			if (styleElm.styleSheet) {
5612 				styleElm.styleSheet.cssText += cssText;
5613 			} else {
5614 				styleElm.appendChild(doc.createTextNode(cssText));
5615 			}
5616 		},
5617 
5618 		loadCSS : function(u) {
5619 			var t = this, d = t.doc, head;
5620 
5621 			if (!u)
5622 				u = '';
5623 
5624 			head = d.getElementsByTagName('head')[0];
5625 
5626 			each(u.split(','), function(u) {
5627 				var link;
5628 
5629 				if (t.files[u])
5630 					return;
5631 
5632 				t.files[u] = true;
5633 				link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
5634 
5635 				// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
5636 				// This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
5637 				// It's ugly but it seems to work fine.
5638 				if (isIE && d.documentMode && d.recalc) {
5639 					link.onload = function() {
5640 						if (d.recalc)
5641 							d.recalc();
5642 
5643 						link.onload = null;
5644 					};
5645 				}
5646 
5647 				head.appendChild(link);
5648 			});
5649 		},
5650 
5651 		addClass : function(e, c) {
5652 			return this.run(e, function(e) {
5653 				var o;
5654 
5655 				if (!c)
5656 					return 0;
5657 
5658 				if (this.hasClass(e, c))
5659 					return e.className;
5660 
5661 				o = this.removeClass(e, c);
5662 
5663 				return e.className = (o != '' ? (o + ' ') : '') + c;
5664 			});
5665 		},
5666 
5667 		removeClass : function(e, c) {
5668 			var t = this, re;
5669 
5670 			return t.run(e, function(e) {
5671 				var v;
5672 
5673 				if (t.hasClass(e, c)) {
5674 					if (!re)
5675 						re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
5676 
5677 					v = e.className.replace(re, ' ');
5678 					v = tinymce.trim(v != ' ' ? v : '');
5679 
5680 					e.className = v;
5681 
5682 					// Empty class attr
5683 					if (!v) {
5684 						e.removeAttribute('class');
5685 						e.removeAttribute('className');
5686 					}
5687 
5688 					return v;
5689 				}
5690 
5691 				return e.className;
5692 			});
5693 		},
5694 
5695 		hasClass : function(n, c) {
5696 			n = this.get(n);
5697 
5698 			if (!n || !c)
5699 				return false;
5700 
5701 			return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
5702 		},
5703 
5704 		show : function(e) {
5705 			return this.setStyle(e, 'display', 'block');
5706 		},
5707 
5708 		hide : function(e) {
5709 			return this.setStyle(e, 'display', 'none');
5710 		},
5711 
5712 		isHidden : function(e) {
5713 			e = this.get(e);
5714 
5715 			return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
5716 		},
5717 
5718 		uniqueId : function(p) {
5719 			return (!p ? 'mce_' : p) + (this.counter++);
5720 		},
5721 
5722 		setHTML : function(element, html) {
5723 			var self = this;
5724 
5725 			return self.run(element, function(element) {
5726 				if (isIE) {
5727 					// Remove all child nodes, IE keeps empty text nodes in DOM
5728 					while (element.firstChild)
5729 						element.removeChild(element.firstChild);
5730 
5731 					try {
5732 						// IE will remove comments from the beginning
5733 						// unless you padd the contents with something
5734 						element.innerHTML = '<br />' + html;
5735 						element.removeChild(element.firstChild);
5736 					} catch (ex) {
5737 						// IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
5738 						// This seems to fix this problem
5739 
5740 						// Create new div with HTML contents and a BR infront to keep comments
5741 						var newElement = self.create('div');
5742 						newElement.innerHTML = '<br />' + html;
5743 
5744 						// Add all children from div to target
5745 						each (tinymce.grep(newElement.childNodes), function(node, i) {
5746 							// Skip br element
5747 							if (i && element.canHaveHTML)
5748 								element.appendChild(node);
5749 						});
5750 					}
5751 				} else
5752 					element.innerHTML = html;
5753 
5754 				return html;
5755 			});
5756 		},
5757 
5758 		getOuterHTML : function(elm) {
5759 			var doc, self = this;
5760 
5761 			elm = self.get(elm);
5762 
5763 			if (!elm)
5764 				return null;
5765 
5766 			if (elm.nodeType === 1 && self.hasOuterHTML)
5767 				return elm.outerHTML;
5768 
5769 			doc = (elm.ownerDocument || self.doc).createElement("body");
5770 			doc.appendChild(elm.cloneNode(true));
5771 
5772 			return doc.innerHTML;
5773 		},
5774 
5775 		setOuterHTML : function(e, h, d) {
5776 			var t = this;
5777 
5778 			function setHTML(e, h, d) {
5779 				var n, tp;
5780 
5781 				tp = d.createElement("body");
5782 				tp.innerHTML = h;
5783 
5784 				n = tp.lastChild;
5785 				while (n) {
5786 					t.insertAfter(n.cloneNode(true), e);
5787 					n = n.previousSibling;
5788 				}
5789 
5790 				t.remove(e);
5791 			};
5792 
5793 			return this.run(e, function(e) {
5794 				e = t.get(e);
5795 
5796 				// Only set HTML on elements
5797 				if (e.nodeType == 1) {
5798 					d = d || e.ownerDocument || t.doc;
5799 
5800 					if (isIE) {
5801 						try {
5802 							// Try outerHTML for IE it sometimes produces an unknown runtime error
5803 							if (isIE && e.nodeType == 1)
5804 								e.outerHTML = h;
5805 							else
5806 								setHTML(e, h, d);
5807 						} catch (ex) {
5808 							// Fix for unknown runtime error
5809 							setHTML(e, h, d);
5810 						}
5811 					} else
5812 						setHTML(e, h, d);
5813 				}
5814 			});
5815 		},
5816 
5817 		decode : Entities.decode,
5818 
5819 		encode : Entities.encodeAllRaw,
5820 
5821 		insertAfter : function(node, reference_node) {
5822 			reference_node = this.get(reference_node);
5823 
5824 			return this.run(node, function(node) {
5825 				var parent, nextSibling;
5826 
5827 				parent = reference_node.parentNode;
5828 				nextSibling = reference_node.nextSibling;
5829 
5830 				if (nextSibling)
5831 					parent.insertBefore(node, nextSibling);
5832 				else
5833 					parent.appendChild(node);
5834 
5835 				return node;
5836 			});
5837 		},
5838 
5839 		replace : function(n, o, k) {
5840 			var t = this;
5841 
5842 			if (is(o, 'array'))
5843 				n = n.cloneNode(true);
5844 
5845 			return t.run(o, function(o) {
5846 				if (k) {
5847 					each(tinymce.grep(o.childNodes), function(c) {
5848 						n.appendChild(c);
5849 					});
5850 				}
5851 
5852 				return o.parentNode.replaceChild(n, o);
5853 			});
5854 		},
5855 
5856 		rename : function(elm, name) {
5857 			var t = this, newElm;
5858 
5859 			if (elm.nodeName != name.toUpperCase()) {
5860 				// Rename block element
5861 				newElm = t.create(name);
5862 
5863 				// Copy attribs to new block
5864 				each(t.getAttribs(elm), function(attr_node) {
5865 					t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
5866 				});
5867 
5868 				// Replace block
5869 				t.replace(newElm, elm, 1);
5870 			}
5871 
5872 			return newElm || elm;
5873 		},
5874 
5875 		findCommonAncestor : function(a, b) {
5876 			var ps = a, pe;
5877 
5878 			while (ps) {
5879 				pe = b;
5880 
5881 				while (pe && ps != pe)
5882 					pe = pe.parentNode;
5883 
5884 				if (ps == pe)
5885 					break;
5886 
5887 				ps = ps.parentNode;
5888 			}
5889 
5890 			if (!ps && a.ownerDocument)
5891 				return a.ownerDocument.documentElement;
5892 
5893 			return ps;
5894 		},
5895 
5896 		toHex : function(s) {
5897 			var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
5898 
5899 			function hex(s) {
5900 				s = parseInt(s, 10).toString(16);
5901 
5902 				return s.length > 1 ? s : '0' + s; // 0 -> 00
5903 			};
5904 
5905 			if (c) {
5906 				s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
5907 
5908 				return s;
5909 			}
5910 
5911 			return s;
5912 		},
5913 
5914 		getClasses : function() {
5915 			var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
5916 
5917 			if (t.classes)
5918 				return t.classes;
5919 
5920 			function addClasses(s) {
5921 				// IE style imports
5922 				each(s.imports, function(r) {
5923 					addClasses(r);
5924 				});
5925 
5926 				each(s.cssRules || s.rules, function(r) {
5927 					// Real type or fake it on IE
5928 					switch (r.type || 1) {
5929 						// Rule
5930 						case 1:
5931 							if (r.selectorText) {
5932 								each(r.selectorText.split(','), function(v) {
5933 									v = v.replace(/^\s*|\s*$|^\s\./g, "");
5934 
5935 									// Is internal or it doesn't contain a class
5936 									if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
5937 										return;
5938 
5939 									// Remove everything but class name
5940 									ov = v;
5941 									v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
5942 
5943 									// Filter classes
5944 									if (f && !(v = f(v, ov)))
5945 										return;
5946 
5947 									if (!lo[v]) {
5948 										cl.push({'class' : v});
5949 										lo[v] = 1;
5950 									}
5951 								});
5952 							}
5953 							break;
5954 
5955 						// Import
5956 						case 3:
5957 							addClasses(r.styleSheet);
5958 							break;
5959 					}
5960 				});
5961 			};
5962 
5963 			try {
5964 				each(t.doc.styleSheets, addClasses);
5965 			} catch (ex) {
5966 				// Ignore
5967 			}
5968 
5969 			if (cl.length > 0)
5970 				t.classes = cl;
5971 
5972 			return cl;
5973 		},
5974 
5975 		run : function(e, f, s) {
5976 			var t = this, o;
5977 
5978 			if (t.doc && typeof(e) === 'string')
5979 				e = t.get(e);
5980 
5981 			if (!e)
5982 				return false;
5983 
5984 			s = s || this;
5985 			if (!e.nodeType && (e.length || e.length === 0)) {
5986 				o = [];
5987 
5988 				each(e, function(e, i) {
5989 					if (e) {
5990 						if (typeof(e) == 'string')
5991 							e = t.doc.getElementById(e);
5992 
5993 						o.push(f.call(s, e, i));
5994 					}
5995 				});
5996 
5997 				return o;
5998 			}
5999 
6000 			return f.call(s, e);
6001 		},
6002 
6003 		getAttribs : function(n) {
6004 			var o;
6005 
6006 			n = this.get(n);
6007 
6008 			if (!n)
6009 				return [];
6010 
6011 			if (isIE) {
6012 				o = [];
6013 
6014 				// Object will throw exception in IE
6015 				if (n.nodeName == 'OBJECT')
6016 					return n.attributes;
6017 
6018 				// IE doesn't keep the selected attribute if you clone option elements
6019 				if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
6020 					o.push({specified : 1, nodeName : 'selected'});
6021 
6022 				// It's crazy that this is faster in IE but it's because it returns all attributes all the time
6023 				n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
6024 					o.push({specified : 1, nodeName : a});
6025 				});
6026 
6027 				return o;
6028 			}
6029 
6030 			return n.attributes;
6031 		},
6032 
6033 		isEmpty : function(node, elements) {
6034 			var self = this, i, attributes, type, walker, name, brCount = 0;
6035 
6036 			node = node.firstChild;
6037 			if (node) {
6038 				walker = new tinymce.dom.TreeWalker(node, node.parentNode);
6039 				elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
6040 
6041 				do {
6042 					type = node.nodeType;
6043 
6044 					if (type === 1) {
6045 						// Ignore bogus elements
6046 						if (node.getAttribute('data-mce-bogus'))
6047 							continue;
6048 
6049 						// Keep empty elements like <img />
6050 						name = node.nodeName.toLowerCase();
6051 						if (elements && elements[name]) {
6052 							// Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
6053 							if (name === 'br') {
6054 								brCount++;
6055 								continue;
6056 							}
6057 
6058 							return false;
6059 						}
6060 
6061 						// Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
6062 						attributes = self.getAttribs(node);
6063 						i = node.attributes.length;
6064 						while (i--) {
6065 							name = node.attributes[i].nodeName;
6066 							if (name === "name" || name === 'data-mce-bookmark')
6067 								return false;
6068 						}
6069 					}
6070 
6071 					// Keep comment nodes
6072 					if (type == 8)
6073 						return false;
6074 
6075 					// Keep non whitespace text nodes
6076 					if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
6077 						return false;
6078 				} while (node = walker.next());
6079 			}
6080 
6081 			return brCount <= 1;
6082 		},
6083 
6084 		destroy : function(s) {
6085 			var t = this;
6086 
6087 			t.win = t.doc = t.root = t.events = t.frag = null;
6088 
6089 			// Manual destroy then remove unload handler
6090 			if (!s)
6091 				tinymce.removeUnload(t.destroy);
6092 		},
6093 
6094 		createRng : function() {
6095 			var d = this.doc;
6096 
6097 			return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
6098 		},
6099 
6100 		nodeIndex : function(node, normalized) {
6101 			var idx = 0, lastNodeType, lastNode, nodeType;
6102 
6103 			if (node) {
6104 				for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
6105 					nodeType = node.nodeType;
6106 
6107 					// Normalize text nodes
6108 					if (normalized && nodeType == 3) {
6109 						if (nodeType == lastNodeType || !node.nodeValue.length)
6110 							continue;
6111 					}
6112 					idx++;
6113 					lastNodeType = nodeType;
6114 				}
6115 			}
6116 
6117 			return idx;
6118 		},
6119 
6120 		split : function(pe, e, re) {
6121 			var t = this, r = t.createRng(), bef, aft, pa;
6122 
6123 			// W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
6124 			// but we don't want that in our code since it serves no purpose for the end user
6125 			// For example if this is chopped:
6126 			//   <p>text 1<span><b>CHOP</b></span>text 2</p>
6127 			// would produce:
6128 			//   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
6129 			// this function will then trim of empty edges and produce:
6130 			//   <p>text 1</p><b>CHOP</b><p>text 2</p>
6131 			function trim(node) {
6132 				var i, children = node.childNodes, type = node.nodeType;
6133 
6134 				function surroundedBySpans(node) {
6135 					var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
6136 					var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
6137 					return previousIsSpan && nextIsSpan;
6138 				}
6139 
6140 				if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
6141 					return;
6142 
6143 				for (i = children.length - 1; i >= 0; i--)
6144 					trim(children[i]);
6145 
6146 				if (type != 9) {
6147 					// Keep non whitespace text nodes
6148 					if (type == 3 && node.nodeValue.length > 0) {
6149 						// If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
6150 						// Also keep text nodes with only spaces if surrounded by spans.
6151 						// eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
6152 						var trimmedLength = tinymce.trim(node.nodeValue).length;
6153 						if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node))
6154 							return;
6155 					} else if (type == 1) {
6156 						// If the only child is a bookmark then move it up
6157 						children = node.childNodes;
6158 						if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
6159 							node.parentNode.insertBefore(children[0], node);
6160 
6161 						// Keep non empty elements or img, hr etc
6162 						if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
6163 							return;
6164 					}
6165 
6166 					t.remove(node);
6167 				}
6168 
6169 				return node;
6170 			};
6171 
6172 			if (pe && e) {
6173 				// Get before chunk
6174 				r.setStart(pe.parentNode, t.nodeIndex(pe));
6175 				r.setEnd(e.parentNode, t.nodeIndex(e));
6176 				bef = r.extractContents();
6177 
6178 				// Get after chunk
6179 				r = t.createRng();
6180 				r.setStart(e.parentNode, t.nodeIndex(e) + 1);
6181 				r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
6182 				aft = r.extractContents();
6183 
6184 				// Insert before chunk
6185 				pa = pe.parentNode;
6186 				pa.insertBefore(trim(bef), pe);
6187 
6188 				// Insert middle chunk
6189 				if (re)
6190 				pa.replaceChild(re, e);
6191 			else
6192 				pa.insertBefore(e, pe);
6193 
6194 				// Insert after chunk
6195 				pa.insertBefore(trim(aft), pe);
6196 				t.remove(pe);
6197 
6198 				return re || e;
6199 			}
6200 		},
6201 
6202 		bind : function(target, name, func, scope) {
6203 			return this.events.add(target, name, func, scope || this);
6204 		},
6205 
6206 		unbind : function(target, name, func) {
6207 			return this.events.remove(target, name, func);
6208 		},
6209 
6210 		fire : function(target, name, evt) {
6211 			return this.events.fire(target, name, evt);
6212 		},
6213 
6214 		// Returns the content editable state of a node
6215 		getContentEditable: function(node) {
6216 			var contentEditable;
6217 
6218 			// Check type
6219 			if (node.nodeType != 1) {
6220 				return null;
6221 			}
6222 
6223 			// Check for fake content editable
6224 			contentEditable = node.getAttribute("data-mce-contenteditable");
6225 			if (contentEditable && contentEditable !== "inherit") {
6226 				return contentEditable;
6227 			}
6228 
6229 			// Check for real content editable
6230 			return node.contentEditable !== "inherit" ? node.contentEditable : null;
6231 		},
6232 
6233 
6234 		_findSib : function(node, selector, name) {
6235 			var t = this, f = selector;
6236 
6237 			if (node) {
6238 				// If expression make a function of it using is
6239 				if (is(f, 'string')) {
6240 					f = function(node) {
6241 						return t.is(node, selector);
6242 					};
6243 				}
6244 
6245 				// Loop all siblings
6246 				for (node = node[name]; node; node = node[name]) {
6247 					if (f(node))
6248 						return node;
6249 				}
6250 			}
6251 
6252 			return null;
6253 		},
6254 
6255 		_isRes : function(c) {
6256 			// Is live resizble element
6257 			return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
6258 		}
6259 
6260 		/*
6261 		walk : function(n, f, s) {
6262 			var d = this.doc, w;
6263 
6264 			if (d.createTreeWalker) {
6265 				w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
6266 
6267 				while ((n = w.nextNode()) != null)
6268 					f.call(s || this, n);
6269 			} else
6270 				tinymce.walk(n, f, 'childNodes', s);
6271 		}
6272 		*/
6273 
6274 		/*
6275 		toRGB : function(s) {
6276 			var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
6277 
6278 			if (c) {
6279 				// #FFF -> #FFFFFF
6280 				if (!is(c[3]))
6281 					c[3] = c[2] = c[1];
6282 
6283 				return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
6284 			}
6285 
6286 			return s;
6287 		}
6288 		*/
6289 	});
6290 
6291 	tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
6292 })(tinymce);
6293 
6294 (function(ns) {
6295 	// Range constructor
6296 	function Range(dom) {
6297 		var t = this,
6298 			doc = dom.doc,
6299 			EXTRACT = 0,
6300 			CLONE = 1,
6301 			DELETE = 2,
6302 			TRUE = true,
6303 			FALSE = false,
6304 			START_OFFSET = 'startOffset',
6305 			START_CONTAINER = 'startContainer',
6306 			END_CONTAINER = 'endContainer',
6307 			END_OFFSET = 'endOffset',
6308 			extend = tinymce.extend,
6309 			nodeIndex = dom.nodeIndex;
6310 
6311 		extend(t, {
6312 			// Inital states
6313 			startContainer : doc,
6314 			startOffset : 0,
6315 			endContainer : doc,
6316 			endOffset : 0,
6317 			collapsed : TRUE,
6318 			commonAncestorContainer : doc,
6319 
6320 			// Range constants
6321 			START_TO_START : 0,
6322 			START_TO_END : 1,
6323 			END_TO_END : 2,
6324 			END_TO_START : 3,
6325 
6326 			// Public methods
6327 			setStart : setStart,
6328 			setEnd : setEnd,
6329 			setStartBefore : setStartBefore,
6330 			setStartAfter : setStartAfter,
6331 			setEndBefore : setEndBefore,
6332 			setEndAfter : setEndAfter,
6333 			collapse : collapse,
6334 			selectNode : selectNode,
6335 			selectNodeContents : selectNodeContents,
6336 			compareBoundaryPoints : compareBoundaryPoints,
6337 			deleteContents : deleteContents,
6338 			extractContents : extractContents,
6339 			cloneContents : cloneContents,
6340 			insertNode : insertNode,
6341 			surroundContents : surroundContents,
6342 			cloneRange : cloneRange,
6343 			toStringIE : toStringIE
6344 		});
6345 
6346 		function createDocumentFragment() {
6347 			return doc.createDocumentFragment();
6348 		};
6349 
6350 		function setStart(n, o) {
6351 			_setEndPoint(TRUE, n, o);
6352 		};
6353 
6354 		function setEnd(n, o) {
6355 			_setEndPoint(FALSE, n, o);
6356 		};
6357 
6358 		function setStartBefore(n) {
6359 			setStart(n.parentNode, nodeIndex(n));
6360 		};
6361 
6362 		function setStartAfter(n) {
6363 			setStart(n.parentNode, nodeIndex(n) + 1);
6364 		};
6365 
6366 		function setEndBefore(n) {
6367 			setEnd(n.parentNode, nodeIndex(n));
6368 		};
6369 
6370 		function setEndAfter(n) {
6371 			setEnd(n.parentNode, nodeIndex(n) + 1);
6372 		};
6373 
6374 		function collapse(ts) {
6375 			if (ts) {
6376 				t[END_CONTAINER] = t[START_CONTAINER];
6377 				t[END_OFFSET] = t[START_OFFSET];
6378 			} else {
6379 				t[START_CONTAINER] = t[END_CONTAINER];
6380 				t[START_OFFSET] = t[END_OFFSET];
6381 			}
6382 
6383 			t.collapsed = TRUE;
6384 		};
6385 
6386 		function selectNode(n) {
6387 			setStartBefore(n);
6388 			setEndAfter(n);
6389 		};
6390 
6391 		function selectNodeContents(n) {
6392 			setStart(n, 0);
6393 			setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
6394 		};
6395 
6396 		function compareBoundaryPoints(h, r) {
6397 			var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
6398 			rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
6399 
6400 			// Check START_TO_START
6401 			if (h === 0)
6402 				return _compareBoundaryPoints(sc, so, rsc, rso);
6403 	
6404 			// Check START_TO_END
6405 			if (h === 1)
6406 				return _compareBoundaryPoints(ec, eo, rsc, rso);
6407 	
6408 			// Check END_TO_END
6409 			if (h === 2)
6410 				return _compareBoundaryPoints(ec, eo, rec, reo);
6411 	
6412 			// Check END_TO_START
6413 			if (h === 3) 
6414 				return _compareBoundaryPoints(sc, so, rec, reo);
6415 		};
6416 
6417 		function deleteContents() {
6418 			_traverse(DELETE);
6419 		};
6420 
6421 		function extractContents() {
6422 			return _traverse(EXTRACT);
6423 		};
6424 
6425 		function cloneContents() {
6426 			return _traverse(CLONE);
6427 		};
6428 
6429 		function insertNode(n) {
6430 			var startContainer = this[START_CONTAINER],
6431 				startOffset = this[START_OFFSET], nn, o;
6432 
6433 			// Node is TEXT_NODE or CDATA
6434 			if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
6435 				if (!startOffset) {
6436 					// At the start of text
6437 					startContainer.parentNode.insertBefore(n, startContainer);
6438 				} else if (startOffset >= startContainer.nodeValue.length) {
6439 					// At the end of text
6440 					dom.insertAfter(n, startContainer);
6441 				} else {
6442 					// Middle, need to split
6443 					nn = startContainer.splitText(startOffset);
6444 					startContainer.parentNode.insertBefore(n, nn);
6445 				}
6446 			} else {
6447 				// Insert element node
6448 				if (startContainer.childNodes.length > 0)
6449 					o = startContainer.childNodes[startOffset];
6450 
6451 				if (o)
6452 					startContainer.insertBefore(n, o);
6453 				else
6454 					startContainer.appendChild(n);
6455 			}
6456 		};
6457 
6458 		function surroundContents(n) {
6459 			var f = t.extractContents();
6460 
6461 			t.insertNode(n);
6462 			n.appendChild(f);
6463 			t.selectNode(n);
6464 		};
6465 
6466 		function cloneRange() {
6467 			return extend(new Range(dom), {
6468 				startContainer : t[START_CONTAINER],
6469 				startOffset : t[START_OFFSET],
6470 				endContainer : t[END_CONTAINER],
6471 				endOffset : t[END_OFFSET],
6472 				collapsed : t.collapsed,
6473 				commonAncestorContainer : t.commonAncestorContainer
6474 			});
6475 		};
6476 
6477 		// Private methods
6478 
6479 		function _getSelectedNode(container, offset) {
6480 			var child;
6481 
6482 			if (container.nodeType == 3 /* TEXT_NODE */)
6483 				return container;
6484 
6485 			if (offset < 0)
6486 				return container;
6487 
6488 			child = container.firstChild;
6489 			while (child && offset > 0) {
6490 				--offset;
6491 				child = child.nextSibling;
6492 			}
6493 
6494 			if (child)
6495 				return child;
6496 
6497 			return container;
6498 		};
6499 
6500 		function _isCollapsed() {
6501 			return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
6502 		};
6503 
6504 		function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
6505 			var c, offsetC, n, cmnRoot, childA, childB;
6506 			
6507 			// In the first case the boundary-points have the same container. A is before B
6508 			// if its offset is less than the offset of B, A is equal to B if its offset is
6509 			// equal to the offset of B, and A is after B if its offset is greater than the
6510 			// offset of B.
6511 			if (containerA == containerB) {
6512 				if (offsetA == offsetB)
6513 					return 0; // equal
6514 
6515 				if (offsetA < offsetB)
6516 					return -1; // before
6517 
6518 				return 1; // after
6519 			}
6520 
6521 			// In the second case a child node C of the container of A is an ancestor
6522 			// container of B. In this case, A is before B if the offset of A is less than or
6523 			// equal to the index of the child node C and A is after B otherwise.
6524 			c = containerB;
6525 			while (c && c.parentNode != containerA)
6526 				c = c.parentNode;
6527 
6528 			if (c) {
6529 				offsetC = 0;
6530 				n = containerA.firstChild;
6531 
6532 				while (n != c && offsetC < offsetA) {
6533 					offsetC++;
6534 					n = n.nextSibling;
6535 				}
6536 
6537 				if (offsetA <= offsetC)
6538 					return -1; // before
6539 
6540 				return 1; // after
6541 			}
6542 
6543 			// In the third case a child node C of the container of B is an ancestor container
6544 			// of A. In this case, A is before B if the index of the child node C is less than
6545 			// the offset of B and A is after B otherwise.
6546 			c = containerA;
6547 			while (c && c.parentNode != containerB) {
6548 				c = c.parentNode;
6549 			}
6550 
6551 			if (c) {
6552 				offsetC = 0;
6553 				n = containerB.firstChild;
6554 
6555 				while (n != c && offsetC < offsetB) {
6556 					offsetC++;
6557 					n = n.nextSibling;
6558 				}
6559 
6560 				if (offsetC < offsetB)
6561 					return -1; // before
6562 
6563 				return 1; // after
6564 			}
6565 
6566 			// In the fourth case, none of three other cases hold: the containers of A and B
6567 			// are siblings or descendants of sibling nodes. In this case, A is before B if
6568 			// the container of A is before the container of B in a pre-order traversal of the
6569 			// Ranges' context tree and A is after B otherwise.
6570 			cmnRoot = dom.findCommonAncestor(containerA, containerB);
6571 			childA = containerA;
6572 
6573 			while (childA && childA.parentNode != cmnRoot)
6574 				childA = childA.parentNode;
6575 
6576 			if (!childA)
6577 				childA = cmnRoot;
6578 
6579 			childB = containerB;
6580 			while (childB && childB.parentNode != cmnRoot)
6581 				childB = childB.parentNode;
6582 
6583 			if (!childB)
6584 				childB = cmnRoot;
6585 
6586 			if (childA == childB)
6587 				return 0; // equal
6588 
6589 			n = cmnRoot.firstChild;
6590 			while (n) {
6591 				if (n == childA)
6592 					return -1; // before
6593 
6594 				if (n == childB)
6595 					return 1; // after
6596 
6597 				n = n.nextSibling;
6598 			}
6599 		};
6600 
6601 		function _setEndPoint(st, n, o) {
6602 			var ec, sc;
6603 
6604 			if (st) {
6605 				t[START_CONTAINER] = n;
6606 				t[START_OFFSET] = o;
6607 			} else {
6608 				t[END_CONTAINER] = n;
6609 				t[END_OFFSET] = o;
6610 			}
6611 
6612 			// If one boundary-point of a Range is set to have a root container
6613 			// other than the current one for the Range, the Range is collapsed to
6614 			// the new position. This enforces the restriction that both boundary-
6615 			// points of a Range must have the same root container.
6616 			ec = t[END_CONTAINER];
6617 			while (ec.parentNode)
6618 				ec = ec.parentNode;
6619 
6620 			sc = t[START_CONTAINER];
6621 			while (sc.parentNode)
6622 				sc = sc.parentNode;
6623 
6624 			if (sc == ec) {
6625 				// The start position of a Range is guaranteed to never be after the
6626 				// end position. To enforce this restriction, if the start is set to
6627 				// be at a position after the end, the Range is collapsed to that
6628 				// position.
6629 				if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
6630 					t.collapse(st);
6631 			} else
6632 				t.collapse(st);
6633 
6634 			t.collapsed = _isCollapsed();
6635 			t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
6636 		};
6637 
6638 		function _traverse(how) {
6639 			var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
6640 
6641 			if (t[START_CONTAINER] == t[END_CONTAINER])
6642 				return _traverseSameContainer(how);
6643 
6644 			for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6645 				if (p == t[START_CONTAINER])
6646 					return _traverseCommonStartContainer(c, how);
6647 
6648 				++endContainerDepth;
6649 			}
6650 
6651 			for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6652 				if (p == t[END_CONTAINER])
6653 					return _traverseCommonEndContainer(c, how);
6654 
6655 				++startContainerDepth;
6656 			}
6657 
6658 			depthDiff = startContainerDepth - endContainerDepth;
6659 
6660 			startNode = t[START_CONTAINER];
6661 			while (depthDiff > 0) {
6662 				startNode = startNode.parentNode;
6663 				depthDiff--;
6664 			}
6665 
6666 			endNode = t[END_CONTAINER];
6667 			while (depthDiff < 0) {
6668 				endNode = endNode.parentNode;
6669 				depthDiff++;
6670 			}
6671 
6672 			// ascend the ancestor hierarchy until we have a common parent.
6673 			for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
6674 				startNode = sp;
6675 				endNode = ep;
6676 			}
6677 
6678 			return _traverseCommonAncestors(startNode, endNode, how);
6679 		};
6680 
6681 		 function _traverseSameContainer(how) {
6682 			var frag, s, sub, n, cnt, sibling, xferNode, start, len;
6683 
6684 			if (how != DELETE)
6685 				frag = createDocumentFragment();
6686 
6687 			// If selection is empty, just return the fragment
6688 			if (t[START_OFFSET] == t[END_OFFSET])
6689 				return frag;
6690 
6691 			// Text node needs special case handling
6692 			if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
6693 				// get the substring
6694 				s = t[START_CONTAINER].nodeValue;
6695 				sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
6696 
6697 				// set the original text node to its new value
6698 				if (how != CLONE) {
6699 					n = t[START_CONTAINER];
6700 					start = t[START_OFFSET];
6701 					len = t[END_OFFSET] - t[START_OFFSET];
6702 
6703 					if (start === 0 && len >= n.nodeValue.length - 1) {
6704 						n.parentNode.removeChild(n);
6705 					} else {
6706 						n.deleteData(start, len);
6707 					}
6708 
6709 					// Nothing is partially selected, so collapse to start point
6710 					t.collapse(TRUE);
6711 				}
6712 
6713 				if (how == DELETE)
6714 					return;
6715 
6716 				if (sub.length > 0) {
6717 					frag.appendChild(doc.createTextNode(sub));
6718 				}
6719 
6720 				return frag;
6721 			}
6722 
6723 			// Copy nodes between the start/end offsets.
6724 			n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
6725 			cnt = t[END_OFFSET] - t[START_OFFSET];
6726 
6727 			while (n && cnt > 0) {
6728 				sibling = n.nextSibling;
6729 				xferNode = _traverseFullySelected(n, how);
6730 
6731 				if (frag)
6732 					frag.appendChild( xferNode );
6733 
6734 				--cnt;
6735 				n = sibling;
6736 			}
6737 
6738 			// Nothing is partially selected, so collapse to start point
6739 			if (how != CLONE)
6740 				t.collapse(TRUE);
6741 
6742 			return frag;
6743 		};
6744 
6745 		function _traverseCommonStartContainer(endAncestor, how) {
6746 			var frag, n, endIdx, cnt, sibling, xferNode;
6747 
6748 			if (how != DELETE)
6749 				frag = createDocumentFragment();
6750 
6751 			n = _traverseRightBoundary(endAncestor, how);
6752 
6753 			if (frag)
6754 				frag.appendChild(n);
6755 
6756 			endIdx = nodeIndex(endAncestor);
6757 			cnt = endIdx - t[START_OFFSET];
6758 
6759 			if (cnt <= 0) {
6760 				// Collapse to just before the endAncestor, which
6761 				// is partially selected.
6762 				if (how != CLONE) {
6763 					t.setEndBefore(endAncestor);
6764 					t.collapse(FALSE);
6765 				}
6766 
6767 				return frag;
6768 			}
6769 
6770 			n = endAncestor.previousSibling;
6771 			while (cnt > 0) {
6772 				sibling = n.previousSibling;
6773 				xferNode = _traverseFullySelected(n, how);
6774 
6775 				if (frag)
6776 					frag.insertBefore(xferNode, frag.firstChild);
6777 
6778 				--cnt;
6779 				n = sibling;
6780 			}
6781 
6782 			// Collapse to just before the endAncestor, which
6783 			// is partially selected.
6784 			if (how != CLONE) {
6785 				t.setEndBefore(endAncestor);
6786 				t.collapse(FALSE);
6787 			}
6788 
6789 			return frag;
6790 		};
6791 
6792 		function _traverseCommonEndContainer(startAncestor, how) {
6793 			var frag, startIdx, n, cnt, sibling, xferNode;
6794 
6795 			if (how != DELETE)
6796 				frag = createDocumentFragment();
6797 
6798 			n = _traverseLeftBoundary(startAncestor, how);
6799 			if (frag)
6800 				frag.appendChild(n);
6801 
6802 			startIdx = nodeIndex(startAncestor);
6803 			++startIdx; // Because we already traversed it
6804 
6805 			cnt = t[END_OFFSET] - startIdx;
6806 			n = startAncestor.nextSibling;
6807 			while (n && cnt > 0) {
6808 				sibling = n.nextSibling;
6809 				xferNode = _traverseFullySelected(n, how);
6810 
6811 				if (frag)
6812 					frag.appendChild(xferNode);
6813 
6814 				--cnt;
6815 				n = sibling;
6816 			}
6817 
6818 			if (how != CLONE) {
6819 				t.setStartAfter(startAncestor);
6820 				t.collapse(TRUE);
6821 			}
6822 
6823 			return frag;
6824 		};
6825 
6826 		function _traverseCommonAncestors(startAncestor, endAncestor, how) {
6827 			var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
6828 
6829 			if (how != DELETE)
6830 				frag = createDocumentFragment();
6831 
6832 			n = _traverseLeftBoundary(startAncestor, how);
6833 			if (frag)
6834 				frag.appendChild(n);
6835 
6836 			commonParent = startAncestor.parentNode;
6837 			startOffset = nodeIndex(startAncestor);
6838 			endOffset = nodeIndex(endAncestor);
6839 			++startOffset;
6840 
6841 			cnt = endOffset - startOffset;
6842 			sibling = startAncestor.nextSibling;
6843 
6844 			while (cnt > 0) {
6845 				nextSibling = sibling.nextSibling;
6846 				n = _traverseFullySelected(sibling, how);
6847 
6848 				if (frag)
6849 					frag.appendChild(n);
6850 
6851 				sibling = nextSibling;
6852 				--cnt;
6853 			}
6854 
6855 			n = _traverseRightBoundary(endAncestor, how);
6856 
6857 			if (frag)
6858 				frag.appendChild(n);
6859 
6860 			if (how != CLONE) {
6861 				t.setStartAfter(startAncestor);
6862 				t.collapse(TRUE);
6863 			}
6864 
6865 			return frag;
6866 		};
6867 
6868 		function _traverseRightBoundary(root, how) {
6869 			var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
6870 
6871 			if (next == root)
6872 				return _traverseNode(next, isFullySelected, FALSE, how);
6873 
6874 			parent = next.parentNode;
6875 			clonedParent = _traverseNode(parent, FALSE, FALSE, how);
6876 
6877 			while (parent) {
6878 				while (next) {
6879 					prevSibling = next.previousSibling;
6880 					clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
6881 
6882 					if (how != DELETE)
6883 						clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
6884 
6885 					isFullySelected = TRUE;
6886 					next = prevSibling;
6887 				}
6888 
6889 				if (parent == root)
6890 					return clonedParent;
6891 
6892 				next = parent.previousSibling;
6893 				parent = parent.parentNode;
6894 
6895 				clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
6896 
6897 				if (how != DELETE)
6898 					clonedGrandParent.appendChild(clonedParent);
6899 
6900 				clonedParent = clonedGrandParent;
6901 			}
6902 		};
6903 
6904 		function _traverseLeftBoundary(root, how) {
6905 			var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
6906 
6907 			if (next == root)
6908 				return _traverseNode(next, isFullySelected, TRUE, how);
6909 
6910 			parent = next.parentNode;
6911 			clonedParent = _traverseNode(parent, FALSE, TRUE, how);
6912 
6913 			while (parent) {
6914 				while (next) {
6915 					nextSibling = next.nextSibling;
6916 					clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
6917 
6918 					if (how != DELETE)
6919 						clonedParent.appendChild(clonedChild);
6920 
6921 					isFullySelected = TRUE;
6922 					next = nextSibling;
6923 				}
6924 
6925 				if (parent == root)
6926 					return clonedParent;
6927 
6928 				next = parent.nextSibling;
6929 				parent = parent.parentNode;
6930 
6931 				clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
6932 
6933 				if (how != DELETE)
6934 					clonedGrandParent.appendChild(clonedParent);
6935 
6936 				clonedParent = clonedGrandParent;
6937 			}
6938 		};
6939 
6940 		function _traverseNode(n, isFullySelected, isLeft, how) {
6941 			var txtValue, newNodeValue, oldNodeValue, offset, newNode;
6942 
6943 			if (isFullySelected)
6944 				return _traverseFullySelected(n, how);
6945 
6946 			if (n.nodeType == 3 /* TEXT_NODE */) {
6947 				txtValue = n.nodeValue;
6948 
6949 				if (isLeft) {
6950 					offset = t[START_OFFSET];
6951 					newNodeValue = txtValue.substring(offset);
6952 					oldNodeValue = txtValue.substring(0, offset);
6953 				} else {
6954 					offset = t[END_OFFSET];
6955 					newNodeValue = txtValue.substring(0, offset);
6956 					oldNodeValue = txtValue.substring(offset);
6957 				}
6958 
6959 				if (how != CLONE)
6960 					n.nodeValue = oldNodeValue;
6961 
6962 				if (how == DELETE)
6963 					return;
6964 
6965 				newNode = dom.clone(n, FALSE);
6966 				newNode.nodeValue = newNodeValue;
6967 
6968 				return newNode;
6969 			}
6970 
6971 			if (how == DELETE)
6972 				return;
6973 
6974 			return dom.clone(n, FALSE);
6975 		};
6976 
6977 		function _traverseFullySelected(n, how) {
6978 			if (how != DELETE)
6979 				return how == CLONE ? dom.clone(n, TRUE) : n;
6980 
6981 			n.parentNode.removeChild(n);
6982 		};
6983 
6984 		function toStringIE() {
6985 			return dom.create('body', null, cloneContents()).outerText;
6986 		}
6987 		
6988 		return t;
6989 	};
6990 
6991 	ns.Range = Range;
6992 
6993 	// Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
6994 	Range.prototype.toString = function() {
6995 		return this.toStringIE();
6996 	};
6997 })(tinymce.dom);
6998 
6999 (function() {
7000 	function Selection(selection) {
7001 		var self = this, dom = selection.dom, TRUE = true, FALSE = false;
7002 
7003 		function getPosition(rng, start) {
7004 			var checkRng, startIndex = 0, endIndex, inside,
7005 				children, child, offset, index, position = -1, parent;
7006 
7007 			// Setup test range, collapse it and get the parent
7008 			checkRng = rng.duplicate();
7009 			checkRng.collapse(start);
7010 			parent = checkRng.parentElement();
7011 
7012 			// Check if the selection is within the right document
7013 			if (parent.ownerDocument !== selection.dom.doc)
7014 				return;
7015 
7016 			// IE will report non editable elements as it's parent so look for an editable one
7017 			while (parent.contentEditable === "false") {
7018 				parent = parent.parentNode;
7019 			}
7020 
7021 			// If parent doesn't have any children then return that we are inside the element
7022 			if (!parent.hasChildNodes()) {
7023 				return {node : parent, inside : 1};
7024 			}
7025 
7026 			// Setup node list and endIndex
7027 			children = parent.children;
7028 			endIndex = children.length - 1;
7029 
7030 			// Perform a binary search for the position
7031 			while (startIndex <= endIndex) {
7032 				index = Math.floor((startIndex + endIndex) / 2);
7033 
7034 				// Move selection to node and compare the ranges
7035 				child = children[index];
7036 				checkRng.moveToElementText(child);
7037 				position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
7038 
7039 				// Before/after or an exact match
7040 				if (position > 0) {
7041 					endIndex = index - 1;
7042 				} else if (position < 0) {
7043 					startIndex = index + 1;
7044 				} else {
7045 					return {node : child};
7046 				}
7047 			}
7048 
7049 			// Check if child position is before or we didn't find a position
7050 			if (position < 0) {
7051 				// No element child was found use the parent element and the offset inside that
7052 				if (!child) {
7053 					checkRng.moveToElementText(parent);
7054 					checkRng.collapse(true);
7055 					child = parent;
7056 					inside = true;
7057 				} else
7058 					checkRng.collapse(false);
7059 
7060 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7061 				// We need to walk char by char since rng.text or rng.htmlText will trim line endings
7062 				offset = 0;
7063 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7064 					if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
7065 						break;
7066 					}
7067 
7068 					offset++;
7069 				}
7070 			} else {
7071 				// Child position is after the selection endpoint
7072 				checkRng.collapse(true);
7073 
7074 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7075 				offset = 0;
7076 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7077 					if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
7078 						break;
7079 					}
7080 
7081 					offset++;
7082 				}
7083 			}
7084 
7085 			return {node : child, position : position, offset : offset, inside : inside};
7086 		};
7087 
7088 		// Returns a W3C DOM compatible range object by using the IE Range API
7089 		function getRange() {
7090 			var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
7091 
7092 			// If selection is outside the current document just return an empty range
7093 			element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
7094 			if (element.ownerDocument != dom.doc)
7095 				return domRange;
7096 
7097 			collapsed = selection.isCollapsed();
7098 
7099 			// Handle control selection
7100 			if (ieRange.item) {
7101 				domRange.setStart(element.parentNode, dom.nodeIndex(element));
7102 				domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
7103 
7104 				return domRange;
7105 			}
7106 
7107 			function findEndPoint(start) {
7108 				var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
7109 
7110 				container = endPoint.node;
7111 				offset = endPoint.offset;
7112 
7113 				if (endPoint.inside && !container.hasChildNodes()) {
7114 					domRange[start ? 'setStart' : 'setEnd'](container, 0);
7115 					return;
7116 				}
7117 
7118 				if (offset === undef) {
7119 					domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
7120 					return;
7121 				}
7122 
7123 				if (endPoint.position < 0) {
7124 					sibling = endPoint.inside ? container.firstChild : container.nextSibling;
7125 
7126 					if (!sibling) {
7127 						domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
7128 						return;
7129 					}
7130 
7131 					if (!offset) {
7132 						if (sibling.nodeType == 3)
7133 							domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
7134 						else
7135 							domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
7136 
7137 						return;
7138 					}
7139 
7140 					// Find the text node and offset
7141 					while (sibling) {
7142 						nodeValue = sibling.nodeValue;
7143 						textNodeOffset += nodeValue.length;
7144 
7145 						// We are at or passed the position we where looking for
7146 						if (textNodeOffset >= offset) {
7147 							container = sibling;
7148 							textNodeOffset -= offset;
7149 							textNodeOffset = nodeValue.length - textNodeOffset;
7150 							break;
7151 						}
7152 
7153 						sibling = sibling.nextSibling;
7154 					}
7155 				} else {
7156 					// Find the text node and offset
7157 					sibling = container.previousSibling;
7158 
7159 					if (!sibling)
7160 						return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
7161 
7162 					// If there isn't any text to loop then use the first position
7163 					if (!offset) {
7164 						if (container.nodeType == 3)
7165 							domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
7166 						else
7167 							domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
7168 
7169 						return;
7170 					}
7171 
7172 					while (sibling) {
7173 						textNodeOffset += sibling.nodeValue.length;
7174 
7175 						// We are at or passed the position we where looking for
7176 						if (textNodeOffset >= offset) {
7177 							container = sibling;
7178 							textNodeOffset -= offset;
7179 							break;
7180 						}
7181 
7182 						sibling = sibling.previousSibling;
7183 					}
7184 				}
7185 
7186 				domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
7187 			};
7188 
7189 			try {
7190 				// Find start point
7191 				findEndPoint(true);
7192 
7193 				// Find end point if needed
7194 				if (!collapsed)
7195 					findEndPoint();
7196 			} catch (ex) {
7197 				// IE has a nasty bug where text nodes might throw "invalid argument" when you
7198 				// access the nodeValue or other properties of text nodes. This seems to happend when
7199 				// text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
7200 				if (ex.number == -2147024809) {
7201 					// Get the current selection
7202 					bookmark = self.getBookmark(2);
7203 
7204 					// Get start element
7205 					tmpRange = ieRange.duplicate();
7206 					tmpRange.collapse(true);
7207 					element = tmpRange.parentElement();
7208 
7209 					// Get end element
7210 					if (!collapsed) {
7211 						tmpRange = ieRange.duplicate();
7212 						tmpRange.collapse(false);
7213 						element2 = tmpRange.parentElement();
7214 						element2.innerHTML = element2.innerHTML;
7215 					}
7216 
7217 					// Remove the broken elements
7218 					element.innerHTML = element.innerHTML;
7219 
7220 					// Restore the selection
7221 					self.moveToBookmark(bookmark);
7222 
7223 					// Since the range has moved we need to re-get it
7224 					ieRange = selection.getRng();
7225 
7226 					// Find start point
7227 					findEndPoint(true);
7228 
7229 					// Find end point if needed
7230 					if (!collapsed)
7231 						findEndPoint();
7232 				} else
7233 					throw ex; // Throw other errors
7234 			}
7235 
7236 			return domRange;
7237 		};
7238 
7239 		this.getBookmark = function(type) {
7240 			var rng = selection.getRng(), start, end, bookmark = {};
7241 
7242 			function getIndexes(node) {
7243 				var parent, root, children, i, indexes = [];
7244 
7245 				parent = node.parentNode;
7246 				root = dom.getRoot().parentNode;
7247 
7248 				while (parent != root && parent.nodeType !== 9) {
7249 					children = parent.children;
7250 
7251 					i = children.length;
7252 					while (i--) {
7253 						if (node === children[i]) {
7254 							indexes.push(i);
7255 							break;
7256 						}
7257 					}
7258 
7259 					node = parent;
7260 					parent = parent.parentNode;
7261 				}
7262 
7263 				return indexes;
7264 			};
7265 
7266 			function getBookmarkEndPoint(start) {
7267 				var position;
7268 
7269 				position = getPosition(rng, start);
7270 				if (position) {
7271 					return {
7272 						position : position.position,
7273 						offset : position.offset,
7274 						indexes : getIndexes(position.node),
7275 						inside : position.inside
7276 					};
7277 				}
7278 			};
7279 
7280 			// Non ubstructive bookmark
7281 			if (type === 2) {
7282 				// Handle text selection
7283 				if (!rng.item) {
7284 					bookmark.start = getBookmarkEndPoint(true);
7285 
7286 					if (!selection.isCollapsed())
7287 						bookmark.end = getBookmarkEndPoint();
7288 				} else
7289 					bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
7290 			}
7291 
7292 			return bookmark;
7293 		};
7294 
7295 		this.moveToBookmark = function(bookmark) {
7296 			var rng, body = dom.doc.body;
7297 
7298 			function resolveIndexes(indexes) {
7299 				var node, i, idx, children;
7300 
7301 				node = dom.getRoot();
7302 				for (i = indexes.length - 1; i >= 0; i--) {
7303 					children = node.children;
7304 					idx = indexes[i];
7305 
7306 					if (idx <= children.length - 1) {
7307 						node = children[idx];
7308 					}
7309 				}
7310 
7311 				return node;
7312 			};
7313 			
7314 			function setBookmarkEndPoint(start) {
7315 				var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
7316 
7317 				if (endPoint) {
7318 					moveLeft = endPoint.position > 0;
7319 
7320 					moveRng = body.createTextRange();
7321 					moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
7322 
7323 					offset = endPoint.offset;
7324 					if (offset !== undef) {
7325 						moveRng.collapse(endPoint.inside || moveLeft);
7326 						moveRng.moveStart('character', moveLeft ? -offset : offset);
7327 					} else
7328 						moveRng.collapse(start);
7329 
7330 					rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
7331 
7332 					if (start)
7333 						rng.collapse(true);
7334 				}
7335 			};
7336 
7337 			if (bookmark.start) {
7338 				if (bookmark.start.ctrl) {
7339 					rng = body.createControlRange();
7340 					rng.addElement(resolveIndexes(bookmark.start.indexes));
7341 					rng.select();
7342 				} else {
7343 					rng = body.createTextRange();
7344 					setBookmarkEndPoint(true);
7345 					setBookmarkEndPoint();
7346 					rng.select();
7347 				}
7348 			}
7349 		};
7350 
7351 		this.addRange = function(rng) {
7352 			var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, doc = selection.dom.doc, body = doc.body;
7353 
7354 			function setEndPoint(start) {
7355 				var container, offset, marker, tmpRng, nodes;
7356 
7357 				marker = dom.create('a');
7358 				container = start ? startContainer : endContainer;
7359 				offset = start ? startOffset : endOffset;
7360 				tmpRng = ieRng.duplicate();
7361 
7362 				if (container == doc || container == doc.documentElement) {
7363 					container = body;
7364 					offset = 0;
7365 				}
7366 
7367 				if (container.nodeType == 3) {
7368 					container.parentNode.insertBefore(marker, container);
7369 					tmpRng.moveToElementText(marker);
7370 					tmpRng.moveStart('character', offset);
7371 					dom.remove(marker);
7372 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7373 				} else {
7374 					nodes = container.childNodes;
7375 
7376 					if (nodes.length) {
7377 						if (offset >= nodes.length) {
7378 							dom.insertAfter(marker, nodes[nodes.length - 1]);
7379 						} else {
7380 							container.insertBefore(marker, nodes[offset]);
7381 						}
7382 
7383 						tmpRng.moveToElementText(marker);
7384 					} else if (container.canHaveHTML) {
7385 						// Empty node selection for example <div>|</div>
7386 						// Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
7387 						container.innerHTML = '<span>\uFEFF</span>';
7388 						marker = container.firstChild;
7389 						tmpRng.moveToElementText(marker);
7390 						tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
7391 					}
7392 
7393 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7394 					dom.remove(marker);
7395 				}
7396 			}
7397 
7398 			// Setup some shorter versions
7399 			startContainer = rng.startContainer;
7400 			startOffset = rng.startOffset;
7401 			endContainer = rng.endContainer;
7402 			endOffset = rng.endOffset;
7403 			ieRng = body.createTextRange();
7404 
7405 			// If single element selection then try making a control selection out of it
7406 			if (startContainer == endContainer && startContainer.nodeType == 1) {
7407 				// Trick to place the caret inside an empty block element like <p></p>
7408 				if (startOffset == endOffset && !startContainer.hasChildNodes()) {
7409 					if (startContainer.canHaveHTML) {
7410 						// Check if previous sibling is an empty block if it is then we need to render it
7411 						// IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
7412 						// Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
7413 						sibling = startContainer.previousSibling;
7414 						if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
7415 							sibling.innerHTML = '\uFEFF';
7416 						} else {
7417 							sibling = null;
7418 						}
7419 
7420 						startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';
7421 						ieRng.moveToElementText(startContainer.lastChild);
7422 						ieRng.select();
7423 						dom.doc.selection.clear();
7424 						startContainer.innerHTML = '';
7425 
7426 						if (sibling) {
7427 							sibling.innerHTML = '';
7428 						}
7429 						return;
7430 					} else {
7431 						startOffset = dom.nodeIndex(startContainer);
7432 						startContainer = startContainer.parentNode;
7433 					}
7434 				}
7435 
7436 				if (startOffset == endOffset - 1) {
7437 					try {
7438 						ctrlRng = body.createControlRange();
7439 						ctrlRng.addElement(startContainer.childNodes[startOffset]);
7440 						ctrlRng.select();
7441 						return;
7442 					} catch (ex) {
7443 						// Ignore
7444 					}
7445 				}
7446 			}
7447 
7448 			// Set start/end point of selection
7449 			setEndPoint(true);
7450 			setEndPoint();
7451 
7452 			// Select the new range and scroll it into view
7453 			ieRng.select();
7454 		};
7455 
7456 		// Expose range method
7457 		this.getRangeAt = getRange;
7458 	};
7459 
7460 	// Expose the selection object
7461 	tinymce.dom.TridentSelection = Selection;
7462 })();
7463 
7464 
7465 /*
7466  * Sizzle CSS Selector Engine
7467  *  Copyright, The Dojo Foundation
7468  *  Released under the MIT, BSD, and GPL Licenses.
7469  *  More information: http://sizzlejs.com/
7470  */
7471 (function(){
7472 
7473 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
7474 	expando = "sizcache",
7475 	done = 0,
7476 	toString = Object.prototype.toString,
7477 	hasDuplicate = false,
7478 	baseHasDuplicate = true,
7479 	rBackslash = /\\/g,
7480 	rReturn = /\r\n/g,
7481 	rNonWord = /\W/;
7482 
7483 // Here we check if the JavaScript engine is using some sort of
7484 // optimization where it does not always call our comparision
7485 // function. If that is the case, discard the hasDuplicate value.
7486 //   Thus far that includes Google Chrome.
7487 [0, 0].sort(function() {
7488 	baseHasDuplicate = false;
7489 	return 0;
7490 });
7491 
7492 var Sizzle = function( selector, context, results, seed ) {
7493 	results = results || [];
7494 	context = context || document;
7495 
7496 	var origContext = context;
7497 
7498 	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
7499 		return [];
7500 	}
7501 
7502 	if ( !selector || typeof selector !== "string" ) {
7503 		return results;
7504 	}
7505 
7506 	var m, set, checkSet, extra, ret, cur, pop, i,
7507 		prune = true,
7508 		contextXML = Sizzle.isXML( context ),
7509 		parts = [],
7510 		soFar = selector;
7511 
7512 	// Reset the position of the chunker regexp (start from head)
7513 	do {
7514 		chunker.exec( "" );
7515 		m = chunker.exec( soFar );
7516 
7517 		if ( m ) {
7518 			soFar = m[3];
7519 
7520 			parts.push( m[1] );
7521 
7522 			if ( m[2] ) {
7523 				extra = m[3];
7524 				break;
7525 			}
7526 		}
7527 	} while ( m );
7528 
7529 	if ( parts.length > 1 && origPOS.exec( selector ) ) {
7530 
7531 		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
7532 			set = posProcess( parts[0] + parts[1], context, seed );
7533 
7534 		} else {
7535 			set = Expr.relative[ parts[0] ] ?
7536 				[ context ] :
7537 				Sizzle( parts.shift(), context );
7538 
7539 			while ( parts.length ) {
7540 				selector = parts.shift();
7541 
7542 				if ( Expr.relative[ selector ] ) {
7543 					selector += parts.shift();
7544 				}
7545 
7546 				set = posProcess( selector, set, seed );
7547 			}
7548 		}
7549 
7550 	} else {
7551 		// Take a shortcut and set the context if the root selector is an ID
7552 		// (but not if it'll be faster if the inner selector is an ID)
7553 		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
7554 				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
7555 
7556 			ret = Sizzle.find( parts.shift(), context, contextXML );
7557 			context = ret.expr ?
7558 				Sizzle.filter( ret.expr, ret.set )[0] :
7559 				ret.set[0];
7560 		}
7561 
7562 		if ( context ) {
7563 			ret = seed ?
7564 				{ expr: parts.pop(), set: makeArray(seed) } :
7565 				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
7566 
7567 			set = ret.expr ?
7568 				Sizzle.filter( ret.expr, ret.set ) :
7569 				ret.set;
7570 
7571 			if ( parts.length > 0 ) {
7572 				checkSet = makeArray( set );
7573 
7574 			} else {
7575 				prune = false;
7576 			}
7577 
7578 			while ( parts.length ) {
7579 				cur = parts.pop();
7580 				pop = cur;
7581 
7582 				if ( !Expr.relative[ cur ] ) {
7583 					cur = "";
7584 				} else {
7585 					pop = parts.pop();
7586 				}
7587 
7588 				if ( pop == null ) {
7589 					pop = context;
7590 				}
7591 
7592 				Expr.relative[ cur ]( checkSet, pop, contextXML );
7593 			}
7594 
7595 		} else {
7596 			checkSet = parts = [];
7597 		}
7598 	}
7599 
7600 	if ( !checkSet ) {
7601 		checkSet = set;
7602 	}
7603 
7604 	if ( !checkSet ) {
7605 		Sizzle.error( cur || selector );
7606 	}
7607 
7608 	if ( toString.call(checkSet) === "[object Array]" ) {
7609 		if ( !prune ) {
7610 			results.push.apply( results, checkSet );
7611 
7612 		} else if ( context && context.nodeType === 1 ) {
7613 			for ( i = 0; checkSet[i] != null; i++ ) {
7614 				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
7615 					results.push( set[i] );
7616 				}
7617 			}
7618 
7619 		} else {
7620 			for ( i = 0; checkSet[i] != null; i++ ) {
7621 				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
7622 					results.push( set[i] );
7623 				}
7624 			}
7625 		}
7626 
7627 	} else {
7628 		makeArray( checkSet, results );
7629 	}
7630 
7631 	if ( extra ) {
7632 		Sizzle( extra, origContext, results, seed );
7633 		Sizzle.uniqueSort( results );
7634 	}
7635 
7636 	return results;
7637 };
7638 
7639 Sizzle.uniqueSort = function( results ) {
7640 	if ( sortOrder ) {
7641 		hasDuplicate = baseHasDuplicate;
7642 		results.sort( sortOrder );
7643 
7644 		if ( hasDuplicate ) {
7645 			for ( var i = 1; i < results.length; i++ ) {
7646 				if ( results[i] === results[ i - 1 ] ) {
7647 					results.splice( i--, 1 );
7648 				}
7649 			}
7650 		}
7651 	}
7652 
7653 	return results;
7654 };
7655 
7656 Sizzle.matches = function( expr, set ) {
7657 	return Sizzle( expr, null, null, set );
7658 };
7659 
7660 Sizzle.matchesSelector = function( node, expr ) {
7661 	return Sizzle( expr, null, null, [node] ).length > 0;
7662 };
7663 
7664 Sizzle.find = function( expr, context, isXML ) {
7665 	var set, i, len, match, type, left;
7666 
7667 	if ( !expr ) {
7668 		return [];
7669 	}
7670 
7671 	for ( i = 0, len = Expr.order.length; i < len; i++ ) {
7672 		type = Expr.order[i];
7673 
7674 		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
7675 			left = match[1];
7676 			match.splice( 1, 1 );
7677 
7678 			if ( left.substr( left.length - 1 ) !== "\\" ) {
7679 				match[1] = (match[1] || "").replace( rBackslash, "" );
7680 				set = Expr.find[ type ]( match, context, isXML );
7681 
7682 				if ( set != null ) {
7683 					expr = expr.replace( Expr.match[ type ], "" );
7684 					break;
7685 				}
7686 			}
7687 		}
7688 	}
7689 
7690 	if ( !set ) {
7691 		set = typeof context.getElementsByTagName !== "undefined" ?
7692 			context.getElementsByTagName( "*" ) :
7693 			[];
7694 	}
7695 
7696 	return { set: set, expr: expr };
7697 };
7698 
7699 Sizzle.filter = function( expr, set, inplace, not ) {
7700 	var match, anyFound,
7701 		type, found, item, filter, left,
7702 		i, pass,
7703 		old = expr,
7704 		result = [],
7705 		curLoop = set,
7706 		isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
7707 
7708 	while ( expr && set.length ) {
7709 		for ( type in Expr.filter ) {
7710 			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
7711 				filter = Expr.filter[ type ];
7712 				left = match[1];
7713 
7714 				anyFound = false;
7715 
7716 				match.splice(1,1);
7717 
7718 				if ( left.substr( left.length - 1 ) === "\\" ) {
7719 					continue;
7720 				}
7721 
7722 				if ( curLoop === result ) {
7723 					result = [];
7724 				}
7725 
7726 				if ( Expr.preFilter[ type ] ) {
7727 					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
7728 
7729 					if ( !match ) {
7730 						anyFound = found = true;
7731 
7732 					} else if ( match === true ) {
7733 						continue;
7734 					}
7735 				}
7736 
7737 				if ( match ) {
7738 					for ( i = 0; (item = curLoop[i]) != null; i++ ) {
7739 						if ( item ) {
7740 							found = filter( item, match, i, curLoop );
7741 							pass = not ^ found;
7742 
7743 							if ( inplace && found != null ) {
7744 								if ( pass ) {
7745 									anyFound = true;
7746 
7747 								} else {
7748 									curLoop[i] = false;
7749 								}
7750 
7751 							} else if ( pass ) {
7752 								result.push( item );
7753 								anyFound = true;
7754 							}
7755 						}
7756 					}
7757 				}
7758 
7759 				if ( found !== undefined ) {
7760 					if ( !inplace ) {
7761 						curLoop = result;
7762 					}
7763 
7764 					expr = expr.replace( Expr.match[ type ], "" );
7765 
7766 					if ( !anyFound ) {
7767 						return [];
7768 					}
7769 
7770 					break;
7771 				}
7772 			}
7773 		}
7774 
7775 		// Improper expression
7776 		if ( expr === old ) {
7777 			if ( anyFound == null ) {
7778 				Sizzle.error( expr );
7779 
7780 			} else {
7781 				break;
7782 			}
7783 		}
7784 
7785 		old = expr;
7786 	}
7787 
7788 	return curLoop;
7789 };
7790 
7791 Sizzle.error = function( msg ) {
7792 	throw new Error( "Syntax error, unrecognized expression: " + msg );
7793 };
7794 
7795 var getText = Sizzle.getText = function( elem ) {
7796     var i, node,
7797 		nodeType = elem.nodeType,
7798 		ret = "";
7799 
7800 	if ( nodeType ) {
7801 		if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
7802 			// Use textContent || innerText for elements
7803 			if ( typeof elem.textContent === 'string' ) {
7804 				return elem.textContent;
7805 			} else if ( typeof elem.innerText === 'string' ) {
7806 				// Replace IE's carriage returns
7807 				return elem.innerText.replace( rReturn, '' );
7808 			} else {
7809 				// Traverse it's children
7810 				for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
7811 					ret += getText( elem );
7812 				}
7813 			}
7814 		} else if ( nodeType === 3 || nodeType === 4 ) {
7815 			return elem.nodeValue;
7816 		}
7817 	} else {
7818 
7819 		// If no nodeType, this is expected to be an array
7820 		for ( i = 0; (node = elem[i]); i++ ) {
7821 			// Do not traverse comment nodes
7822 			if ( node.nodeType !== 8 ) {
7823 				ret += getText( node );
7824 			}
7825 		}
7826 	}
7827 	return ret;
7828 };
7829 
7830 var Expr = Sizzle.selectors = {
7831 	order: [ "ID", "NAME", "TAG" ],
7832 
7833 	match: {
7834 		ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
7835 		CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
7836 		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
7837 		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
7838 		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
7839 		CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
7840 		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
7841 		PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
7842 	},
7843 
7844 	leftMatch: {},
7845 
7846 	attrMap: {
7847 		"class": "className",
7848 		"for": "htmlFor"
7849 	},
7850 
7851 	attrHandle: {
7852 		href: function( elem ) {
7853 			return elem.getAttribute( "href" );
7854 		},
7855 		type: function( elem ) {
7856 			return elem.getAttribute( "type" );
7857 		}
7858 	},
7859 
7860 	relative: {
7861 		"+": function(checkSet, part){
7862 			var isPartStr = typeof part === "string",
7863 				isTag = isPartStr && !rNonWord.test( part ),
7864 				isPartStrNotTag = isPartStr && !isTag;
7865 
7866 			if ( isTag ) {
7867 				part = part.toLowerCase();
7868 			}
7869 
7870 			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
7871 				if ( (elem = checkSet[i]) ) {
7872 					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
7873 
7874 					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
7875 						elem || false :
7876 						elem === part;
7877 				}
7878 			}
7879 
7880 			if ( isPartStrNotTag ) {
7881 				Sizzle.filter( part, checkSet, true );
7882 			}
7883 		},
7884 
7885 		">": function( checkSet, part ) {
7886 			var elem,
7887 				isPartStr = typeof part === "string",
7888 				i = 0,
7889 				l = checkSet.length;
7890 
7891 			if ( isPartStr && !rNonWord.test( part ) ) {
7892 				part = part.toLowerCase();
7893 
7894 				for ( ; i < l; i++ ) {
7895 					elem = checkSet[i];
7896 
7897 					if ( elem ) {
7898 						var parent = elem.parentNode;
7899 						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
7900 					}
7901 				}
7902 
7903 			} else {
7904 				for ( ; i < l; i++ ) {
7905 					elem = checkSet[i];
7906 
7907 					if ( elem ) {
7908 						checkSet[i] = isPartStr ?
7909 							elem.parentNode :
7910 							elem.parentNode === part;
7911 					}
7912 				}
7913 
7914 				if ( isPartStr ) {
7915 					Sizzle.filter( part, checkSet, true );
7916 				}
7917 			}
7918 		},
7919 
7920 		"": function(checkSet, part, isXML){
7921 			var nodeCheck,
7922 				doneName = done++,
7923 				checkFn = dirCheck;
7924 
7925 			if ( typeof part === "string" && !rNonWord.test( part ) ) {
7926 				part = part.toLowerCase();
7927 				nodeCheck = part;
7928 				checkFn = dirNodeCheck;
7929 			}
7930 
7931 			checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
7932 		},
7933 
7934 		"~": function( checkSet, part, isXML ) {
7935 			var nodeCheck,
7936 				doneName = done++,
7937 				checkFn = dirCheck;
7938 
7939 			if ( typeof part === "string" && !rNonWord.test( part ) ) {
7940 				part = part.toLowerCase();
7941 				nodeCheck = part;
7942 				checkFn = dirNodeCheck;
7943 			}
7944 
7945 			checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
7946 		}
7947 	},
7948 
7949 	find: {
7950 		ID: function( match, context, isXML ) {
7951 			if ( typeof context.getElementById !== "undefined" && !isXML ) {
7952 				var m = context.getElementById(match[1]);
7953 				// Check parentNode to catch when Blackberry 4.6 returns
7954 				// nodes that are no longer in the document #6963
7955 				return m && m.parentNode ? [m] : [];
7956 			}
7957 		},
7958 
7959 		NAME: function( match, context ) {
7960 			if ( typeof context.getElementsByName !== "undefined" ) {
7961 				var ret = [],
7962 					results = context.getElementsByName( match[1] );
7963 
7964 				for ( var i = 0, l = results.length; i < l; i++ ) {
7965 					if ( results[i].getAttribute("name") === match[1] ) {
7966 						ret.push( results[i] );
7967 					}
7968 				}
7969 
7970 				return ret.length === 0 ? null : ret;
7971 			}
7972 		},
7973 
7974 		TAG: function( match, context ) {
7975 			if ( typeof context.getElementsByTagName !== "undefined" ) {
7976 				return context.getElementsByTagName( match[1] );
7977 			}
7978 		}
7979 	},
7980 	preFilter: {
7981 		CLASS: function( match, curLoop, inplace, result, not, isXML ) {
7982 			match = " " + match[1].replace( rBackslash, "" ) + " ";
7983 
7984 			if ( isXML ) {
7985 				return match;
7986 			}
7987 
7988 			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
7989 				if ( elem ) {
7990 					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
7991 						if ( !inplace ) {
7992 							result.push( elem );
7993 						}
7994 
7995 					} else if ( inplace ) {
7996 						curLoop[i] = false;
7997 					}
7998 				}
7999 			}
8000 
8001 			return false;
8002 		},
8003 
8004 		ID: function( match ) {
8005 			return match[1].replace( rBackslash, "" );
8006 		},
8007 
8008 		TAG: function( match, curLoop ) {
8009 			return match[1].replace( rBackslash, "" ).toLowerCase();
8010 		},
8011 
8012 		CHILD: function( match ) {
8013 			if ( match[1] === "nth" ) {
8014 				if ( !match[2] ) {
8015 					Sizzle.error( match[0] );
8016 				}
8017 
8018 				match[2] = match[2].replace(/^\+|\s*/g, '');
8019 
8020 				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
8021 				var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
8022 					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
8023 					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
8024 
8025 				// calculate the numbers (first)n+(last) including if they are negative
8026 				match[2] = (test[1] + (test[2] || 1)) - 0;
8027 				match[3] = test[3] - 0;
8028 			}
8029 			else if ( match[2] ) {
8030 				Sizzle.error( match[0] );
8031 			}
8032 
8033 			// TODO: Move to normal caching system
8034 			match[0] = done++;
8035 
8036 			return match;
8037 		},
8038 
8039 		ATTR: function( match, curLoop, inplace, result, not, isXML ) {
8040 			var name = match[1] = match[1].replace( rBackslash, "" );
8041 
8042 			if ( !isXML && Expr.attrMap[name] ) {
8043 				match[1] = Expr.attrMap[name];
8044 			}
8045 
8046 			// Handle if an un-quoted value was used
8047 			match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
8048 
8049 			if ( match[2] === "~=" ) {
8050 				match[4] = " " + match[4] + " ";
8051 			}
8052 
8053 			return match;
8054 		},
8055 
8056 		PSEUDO: function( match, curLoop, inplace, result, not ) {
8057 			if ( match[1] === "not" ) {
8058 				// If we're dealing with a complex expression, or a simple one
8059 				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
8060 					match[3] = Sizzle(match[3], null, null, curLoop);
8061 
8062 				} else {
8063 					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
8064 
8065 					if ( !inplace ) {
8066 						result.push.apply( result, ret );
8067 					}
8068 
8069 					return false;
8070 				}
8071 
8072 			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
8073 				return true;
8074 			}
8075 
8076 			return match;
8077 		},
8078 
8079 		POS: function( match ) {
8080 			match.unshift( true );
8081 
8082 			return match;
8083 		}
8084 	},
8085 
8086 	filters: {
8087 		enabled: function( elem ) {
8088 			return elem.disabled === false && elem.type !== "hidden";
8089 		},
8090 
8091 		disabled: function( elem ) {
8092 			return elem.disabled === true;
8093 		},
8094 
8095 		checked: function( elem ) {
8096 			return elem.checked === true;
8097 		},
8098 
8099 		selected: function( elem ) {
8100 			// Accessing this property makes selected-by-default
8101 			// options in Safari work properly
8102 			if ( elem.parentNode ) {
8103 				elem.parentNode.selectedIndex;
8104 			}
8105 
8106 			return elem.selected === true;
8107 		},
8108 
8109 		parent: function( elem ) {
8110 			return !!elem.firstChild;
8111 		},
8112 
8113 		empty: function( elem ) {
8114 			return !elem.firstChild;
8115 		},
8116 
8117 		has: function( elem, i, match ) {
8118 			return !!Sizzle( match[3], elem ).length;
8119 		},
8120 
8121 		header: function( elem ) {
8122 			return (/h\d/i).test( elem.nodeName );
8123 		},
8124 
8125 		text: function( elem ) {
8126 			var attr = elem.getAttribute( "type" ), type = elem.type;
8127 			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
8128 			// use getAttribute instead to test this case
8129 			return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
8130 		},
8131 
8132 		radio: function( elem ) {
8133 			return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
8134 		},
8135 
8136 		checkbox: function( elem ) {
8137 			return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
8138 		},
8139 
8140 		file: function( elem ) {
8141 			return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
8142 		},
8143 
8144 		password: function( elem ) {
8145 			return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
8146 		},
8147 
8148 		submit: function( elem ) {
8149 			var name = elem.nodeName.toLowerCase();
8150 			return (name === "input" || name === "button") && "submit" === elem.type;
8151 		},
8152 
8153 		image: function( elem ) {
8154 			return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
8155 		},
8156 
8157 		reset: function( elem ) {
8158 			var name = elem.nodeName.toLowerCase();
8159 			return (name === "input" || name === "button") && "reset" === elem.type;
8160 		},
8161 
8162 		button: function( elem ) {
8163 			var name = elem.nodeName.toLowerCase();
8164 			return name === "input" && "button" === elem.type || name === "button";
8165 		},
8166 
8167 		input: function( elem ) {
8168 			return (/input|select|textarea|button/i).test( elem.nodeName );
8169 		},
8170 
8171 		focus: function( elem ) {
8172 			return elem === elem.ownerDocument.activeElement;
8173 		}
8174 	},
8175 	setFilters: {
8176 		first: function( elem, i ) {
8177 			return i === 0;
8178 		},
8179 
8180 		last: function( elem, i, match, array ) {
8181 			return i === array.length - 1;
8182 		},
8183 
8184 		even: function( elem, i ) {
8185 			return i % 2 === 0;
8186 		},
8187 
8188 		odd: function( elem, i ) {
8189 			return i % 2 === 1;
8190 		},
8191 
8192 		lt: function( elem, i, match ) {
8193 			return i < match[3] - 0;
8194 		},
8195 
8196 		gt: function( elem, i, match ) {
8197 			return i > match[3] - 0;
8198 		},
8199 
8200 		nth: function( elem, i, match ) {
8201 			return match[3] - 0 === i;
8202 		},
8203 
8204 		eq: function( elem, i, match ) {
8205 			return match[3] - 0 === i;
8206 		}
8207 	},
8208 	filter: {
8209 		PSEUDO: function( elem, match, i, array ) {
8210 			var name = match[1],
8211 				filter = Expr.filters[ name ];
8212 
8213 			if ( filter ) {
8214 				return filter( elem, i, match, array );
8215 
8216 			} else if ( name === "contains" ) {
8217 				return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
8218 
8219 			} else if ( name === "not" ) {
8220 				var not = match[3];
8221 
8222 				for ( var j = 0, l = not.length; j < l; j++ ) {
8223 					if ( not[j] === elem ) {
8224 						return false;
8225 					}
8226 				}
8227 
8228 				return true;
8229 
8230 			} else {
8231 				Sizzle.error( name );
8232 			}
8233 		},
8234 
8235 		CHILD: function( elem, match ) {
8236 			var first, last,
8237 				doneName, parent, cache,
8238 				count, diff,
8239 				type = match[1],
8240 				node = elem;
8241 
8242 			switch ( type ) {
8243 				case "only":
8244 				case "first":
8245 					while ( (node = node.previousSibling) ) {
8246 						if ( node.nodeType === 1 ) {
8247 							return false;
8248 						}
8249 					}
8250 
8251 					if ( type === "first" ) {
8252 						return true;
8253 					}
8254 
8255 					node = elem;
8256 
8257 					/* falls through */
8258 				case "last":
8259 					while ( (node = node.nextSibling) ) {
8260 						if ( node.nodeType === 1 ) {
8261 							return false;
8262 						}
8263 					}
8264 
8265 					return true;
8266 
8267 				case "nth":
8268 					first = match[2];
8269 					last = match[3];
8270 
8271 					if ( first === 1 && last === 0 ) {
8272 						return true;
8273 					}
8274 
8275 					doneName = match[0];
8276 					parent = elem.parentNode;
8277 
8278 					if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
8279 						count = 0;
8280 
8281 						for ( node = parent.firstChild; node; node = node.nextSibling ) {
8282 							if ( node.nodeType === 1 ) {
8283 								node.nodeIndex = ++count;
8284 							}
8285 						}
8286 
8287 						parent[ expando ] = doneName;
8288 					}
8289 
8290 					diff = elem.nodeIndex - last;
8291 
8292 					if ( first === 0 ) {
8293 						return diff === 0;
8294 
8295 					} else {
8296 						return ( diff % first === 0 && diff / first >= 0 );
8297 					}
8298 			}
8299 		},
8300 
8301 		ID: function( elem, match ) {
8302 			return elem.nodeType === 1 && elem.getAttribute("id") === match;
8303 		},
8304 
8305 		TAG: function( elem, match ) {
8306 			return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
8307 		},
8308 
8309 		CLASS: function( elem, match ) {
8310 			return (" " + (elem.className || elem.getAttribute("class")) + " ")
8311 				.indexOf( match ) > -1;
8312 		},
8313 
8314 		ATTR: function( elem, match ) {
8315 			var name = match[1],
8316 				result = Sizzle.attr ?
8317 					Sizzle.attr( elem, name ) :
8318 					Expr.attrHandle[ name ] ?
8319 					Expr.attrHandle[ name ]( elem ) :
8320 					elem[ name ] != null ?
8321 						elem[ name ] :
8322 						elem.getAttribute( name ),
8323 				value = result + "",
8324 				type = match[2],
8325 				check = match[4];
8326 
8327 			return result == null ?
8328 				type === "!=" :
8329 				!type && Sizzle.attr ?
8330 				result != null :
8331 				type === "=" ?
8332 				value === check :
8333 				type === "*=" ?
8334 				value.indexOf(check) >= 0 :
8335 				type === "~=" ?
8336 				(" " + value + " ").indexOf(check) >= 0 :
8337 				!check ?
8338 				value && result !== false :
8339 				type === "!=" ?
8340 				value !== check :
8341 				type === "^=" ?
8342 				value.indexOf(check) === 0 :
8343 				type === "$=" ?
8344 				value.substr(value.length - check.length) === check :
8345 				type === "|=" ?
8346 				value === check || value.substr(0, check.length + 1) === check + "-" :
8347 				false;
8348 		},
8349 
8350 		POS: function( elem, match, i, array ) {
8351 			var name = match[2],
8352 				filter = Expr.setFilters[ name ];
8353 
8354 			if ( filter ) {
8355 				return filter( elem, i, match, array );
8356 			}
8357 		}
8358 	}
8359 };
8360 
8361 var origPOS = Expr.match.POS,
8362 	fescape = function(all, num){
8363 		return "\\" + (num - 0 + 1);
8364 	};
8365 
8366 for ( var type in Expr.match ) {
8367 	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
8368 	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
8369 }
8370 // Expose origPOS
8371 // "global" as in regardless of relation to brackets/parens
8372 Expr.match.globalPOS = origPOS;
8373 
8374 var makeArray = function( array, results ) {
8375 	array = Array.prototype.slice.call( array, 0 );
8376 
8377 	if ( results ) {
8378 		results.push.apply( results, array );
8379 		return results;
8380 	}
8381 
8382 	return array;
8383 };
8384 
8385 // Perform a simple check to determine if the browser is capable of
8386 // converting a NodeList to an array using builtin methods.
8387 // Also verifies that the returned array holds DOM nodes
8388 // (which is not the case in the Blackberry browser)
8389 try {
8390 	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
8391 
8392 // Provide a fallback method if it does not work
8393 } catch( e ) {
8394 	makeArray = function( array, results ) {
8395 		var i = 0,
8396 			ret = results || [];
8397 
8398 		if ( toString.call(array) === "[object Array]" ) {
8399 			Array.prototype.push.apply( ret, array );
8400 
8401 		} else {
8402 			if ( typeof array.length === "number" ) {
8403 				for ( var l = array.length; i < l; i++ ) {
8404 					ret.push( array[i] );
8405 				}
8406 
8407 			} else {
8408 				for ( ; array[i]; i++ ) {
8409 					ret.push( array[i] );
8410 				}
8411 			}
8412 		}
8413 
8414 		return ret;
8415 	};
8416 }
8417 
8418 var sortOrder, siblingCheck;
8419 
8420 if ( document.documentElement.compareDocumentPosition ) {
8421 	sortOrder = function( a, b ) {
8422 		if ( a === b ) {
8423 			hasDuplicate = true;
8424 			return 0;
8425 		}
8426 
8427 		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
8428 			return a.compareDocumentPosition ? -1 : 1;
8429 		}
8430 
8431 		return a.compareDocumentPosition(b) & 4 ? -1 : 1;
8432 	};
8433 
8434 } else {
8435 	sortOrder = function( a, b ) {
8436 		// The nodes are identical, we can exit early
8437 		if ( a === b ) {
8438 			hasDuplicate = true;
8439 			return 0;
8440 
8441 		// Fallback to using sourceIndex (in IE) if it's available on both nodes
8442 		} else if ( a.sourceIndex && b.sourceIndex ) {
8443 			return a.sourceIndex - b.sourceIndex;
8444 		}
8445 
8446 		var al, bl,
8447 			ap = [],
8448 			bp = [],
8449 			aup = a.parentNode,
8450 			bup = b.parentNode,
8451 			cur = aup;
8452 
8453 		// If the nodes are siblings (or identical) we can do a quick check
8454 		if ( aup === bup ) {
8455 			return siblingCheck( a, b );
8456 
8457 		// If no parents were found then the nodes are disconnected
8458 		} else if ( !aup ) {
8459 			return -1;
8460 
8461 		} else if ( !bup ) {
8462 			return 1;
8463 		}
8464 
8465 		// Otherwise they're somewhere else in the tree so we need
8466 		// to build up a full list of the parentNodes for comparison
8467 		while ( cur ) {
8468 			ap.unshift( cur );
8469 			cur = cur.parentNode;
8470 		}
8471 
8472 		cur = bup;
8473 
8474 		while ( cur ) {
8475 			bp.unshift( cur );
8476 			cur = cur.parentNode;
8477 		}
8478 
8479 		al = ap.length;
8480 		bl = bp.length;
8481 
8482 		// Start walking down the tree looking for a discrepancy
8483 		for ( var i = 0; i < al && i < bl; i++ ) {
8484 			if ( ap[i] !== bp[i] ) {
8485 				return siblingCheck( ap[i], bp[i] );
8486 			}
8487 		}
8488 
8489 		// We ended someplace up the tree so do a sibling check
8490 		return i === al ?
8491 			siblingCheck( a, bp[i], -1 ) :
8492 			siblingCheck( ap[i], b, 1 );
8493 	};
8494 
8495 	siblingCheck = function( a, b, ret ) {
8496 		if ( a === b ) {
8497 			return ret;
8498 		}
8499 
8500 		var cur = a.nextSibling;
8501 
8502 		while ( cur ) {
8503 			if ( cur === b ) {
8504 				return -1;
8505 			}
8506 
8507 			cur = cur.nextSibling;
8508 		}
8509 
8510 		return 1;
8511 	};
8512 }
8513 
8514 // Check to see if the browser returns elements by name when
8515 // querying by getElementById (and provide a workaround)
8516 (function(){
8517 	// We're going to inject a fake input element with a specified name
8518 	var form = document.createElement("div"),
8519 		id = "script" + (new Date()).getTime(),
8520 		root = document.documentElement;
8521 
8522 	form.innerHTML = "<a name='" + id + "'/>";
8523 
8524 	// Inject it into the root element, check its status, and remove it quickly
8525 	root.insertBefore( form, root.firstChild );
8526 
8527 	// The workaround has to do additional checks after a getElementById
8528 	// Which slows things down for other browsers (hence the branching)
8529 	if ( document.getElementById( id ) ) {
8530 		Expr.find.ID = function( match, context, isXML ) {
8531 			if ( typeof context.getElementById !== "undefined" && !isXML ) {
8532 				var m = context.getElementById(match[1]);
8533 
8534 				return m ?
8535 					m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
8536 						[m] :
8537 						undefined :
8538 					[];
8539 			}
8540 		};
8541 
8542 		Expr.filter.ID = function( elem, match ) {
8543 			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
8544 
8545 			return elem.nodeType === 1 && node && node.nodeValue === match;
8546 		};
8547 	}
8548 
8549 	root.removeChild( form );
8550 
8551 	// release memory in IE
8552 	root = form = null;
8553 })();
8554 
8555 (function(){
8556 	// Check to see if the browser returns only elements
8557 	// when doing getElementsByTagName("*")
8558 
8559 	// Create a fake element
8560 	var div = document.createElement("div");
8561 	div.appendChild( document.createComment("") );
8562 
8563 	// Make sure no comments are found
8564 	if ( div.getElementsByTagName("*").length > 0 ) {
8565 		Expr.find.TAG = function( match, context ) {
8566 			var results = context.getElementsByTagName( match[1] );
8567 
8568 			// Filter out possible comments
8569 			if ( match[1] === "*" ) {
8570 				var tmp = [];
8571 
8572 				for ( var i = 0; results[i]; i++ ) {
8573 					if ( results[i].nodeType === 1 ) {
8574 						tmp.push( results[i] );
8575 					}
8576 				}
8577 
8578 				results = tmp;
8579 			}
8580 
8581 			return results;
8582 		};
8583 	}
8584 
8585 	// Check to see if an attribute returns normalized href attributes
8586 	div.innerHTML = "<a href='#'></a>";
8587 
8588 	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
8589 			div.firstChild.getAttribute("href") !== "#" ) {
8590 
8591 		Expr.attrHandle.href = function( elem ) {
8592 			return elem.getAttribute( "href", 2 );
8593 		};
8594 	}
8595 
8596 	// release memory in IE
8597 	div = null;
8598 })();
8599 
8600 if ( document.querySelectorAll ) {
8601 	(function(){
8602 		var oldSizzle = Sizzle,
8603 			div = document.createElement("div"),
8604 			id = "__sizzle__";
8605 
8606 		div.innerHTML = "<p class='TEST'></p>";
8607 
8608 		// Safari can't handle uppercase or unicode characters when
8609 		// in quirks mode.
8610 		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
8611 			return;
8612 		}
8613 
8614 		Sizzle = function( query, context, extra, seed ) {
8615 			context = context || document;
8616 
8617 			// Only use querySelectorAll on non-XML documents
8618 			// (ID selectors don't work in non-HTML documents)
8619 			if ( !seed && !Sizzle.isXML(context) ) {
8620 				// See if we find a selector to speed up
8621 				var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
8622 
8623 				if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
8624 					// Speed-up: Sizzle("TAG")
8625 					if ( match[1] ) {
8626 						return makeArray( context.getElementsByTagName( query ), extra );
8627 
8628 					// Speed-up: Sizzle(".CLASS")
8629 					} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
8630 						return makeArray( context.getElementsByClassName( match[2] ), extra );
8631 					}
8632 				}
8633 
8634 				if ( context.nodeType === 9 ) {
8635 					// Speed-up: Sizzle("body")
8636 					// The body element only exists once, optimize finding it
8637 					if ( query === "body" && context.body ) {
8638 						return makeArray( [ context.body ], extra );
8639 
8640 					// Speed-up: Sizzle("#ID")
8641 					} else if ( match && match[3] ) {
8642 						var elem = context.getElementById( match[3] );
8643 
8644 						// Check parentNode to catch when Blackberry 4.6 returns
8645 						// nodes that are no longer in the document #6963
8646 						if ( elem && elem.parentNode ) {
8647 							// Handle the case where IE and Opera return items
8648 							// by name instead of ID
8649 							if ( elem.id === match[3] ) {
8650 								return makeArray( [ elem ], extra );
8651 							}
8652 
8653 						} else {
8654 							return makeArray( [], extra );
8655 						}
8656 					}
8657 
8658 					try {
8659 						return makeArray( context.querySelectorAll(query), extra );
8660 					} catch(qsaError) {}
8661 
8662 				// qSA works strangely on Element-rooted queries
8663 				// We can work around this by specifying an extra ID on the root
8664 				// and working up from there (Thanks to Andrew Dupont for the technique)
8665 				// IE 8 doesn't work on object elements
8666 				} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
8667 					var oldContext = context,
8668 						old = context.getAttribute( "id" ),
8669 						nid = old || id,
8670 						hasParent = context.parentNode,
8671 						relativeHierarchySelector = /^\s*[+~]/.test( query );
8672 
8673 					if ( !old ) {
8674 						context.setAttribute( "id", nid );
8675 					} else {
8676 						nid = nid.replace( /'/g, "\\$&" );
8677 					}
8678 					if ( relativeHierarchySelector && hasParent ) {
8679 						context = context.parentNode;
8680 					}
8681 
8682 					try {
8683 						if ( !relativeHierarchySelector || hasParent ) {
8684 							return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
8685 						}
8686 
8687 					} catch(pseudoError) {
8688 					} finally {
8689 						if ( !old ) {
8690 							oldContext.removeAttribute( "id" );
8691 						}
8692 					}
8693 				}
8694 			}
8695 
8696 			return oldSizzle(query, context, extra, seed);
8697 		};
8698 
8699 		for ( var prop in oldSizzle ) {
8700 			Sizzle[ prop ] = oldSizzle[ prop ];
8701 		}
8702 
8703 		// release memory in IE
8704 		div = null;
8705 	})();
8706 }
8707 
8708 (function(){
8709 	var html = document.documentElement,
8710 		matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
8711 
8712 	if ( matches ) {
8713 		// Check to see if it's possible to do matchesSelector
8714 		// on a disconnected node (IE 9 fails this)
8715 		var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
8716 			pseudoWorks = false;
8717 
8718 		try {
8719 			// This should fail with an exception
8720 			// Gecko does not error, returns false instead
8721 			matches.call( document.documentElement, "[test!='']:sizzle" );
8722 
8723 		} catch( pseudoError ) {
8724 			pseudoWorks = true;
8725 		}
8726 
8727 		Sizzle.matchesSelector = function( node, expr ) {
8728 			// Make sure that attribute selectors are quoted
8729 			expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
8730 
8731 			if ( !Sizzle.isXML( node ) ) {
8732 				try {
8733 					if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
8734 						var ret = matches.call( node, expr );
8735 
8736 						// IE 9's matchesSelector returns false on disconnected nodes
8737 						if ( ret || !disconnectedMatch ||
8738 								// As well, disconnected nodes are said to be in a document
8739 								// fragment in IE 9, so check for that
8740 								node.document && node.document.nodeType !== 11 ) {
8741 							return ret;
8742 						}
8743 					}
8744 				} catch(e) {}
8745 			}
8746 
8747 			return Sizzle(expr, null, null, [node]).length > 0;
8748 		};
8749 	}
8750 })();
8751 
8752 (function(){
8753 	var div = document.createElement("div");
8754 
8755 	div.innerHTML = "<div class='test e'></div><div class='test'></div>";
8756 
8757 	// Opera can't find a second classname (in 9.6)
8758 	// Also, make sure that getElementsByClassName actually exists
8759 	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
8760 		return;
8761 	}
8762 
8763 	// Safari caches class attributes, doesn't catch changes (in 3.2)
8764 	div.lastChild.className = "e";
8765 
8766 	if ( div.getElementsByClassName("e").length === 1 ) {
8767 		return;
8768 	}
8769 
8770 	Expr.order.splice(1, 0, "CLASS");
8771 	Expr.find.CLASS = function( match, context, isXML ) {
8772 		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
8773 			return context.getElementsByClassName(match[1]);
8774 		}
8775 	};
8776 
8777 	// release memory in IE
8778 	div = null;
8779 })();
8780 
8781 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
8782 	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
8783 		var elem = checkSet[i];
8784 
8785 		if ( elem ) {
8786 			var match = false;
8787 
8788 			elem = elem[dir];
8789 
8790 			while ( elem ) {
8791 				if ( elem[ expando ] === doneName ) {
8792 					match = checkSet[elem.sizset];
8793 					break;
8794 				}
8795 
8796 				if ( elem.nodeType === 1 && !isXML ){
8797 					elem[ expando ] = doneName;
8798 					elem.sizset = i;
8799 				}
8800 
8801 				if ( elem.nodeName.toLowerCase() === cur ) {
8802 					match = elem;
8803 					break;
8804 				}
8805 
8806 				elem = elem[dir];
8807 			}
8808 
8809 			checkSet[i] = match;
8810 		}
8811 	}
8812 }
8813 
8814 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
8815 	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
8816 		var elem = checkSet[i];
8817 
8818 		if ( elem ) {
8819 			var match = false;
8820 
8821 			elem = elem[dir];
8822 
8823 			while ( elem ) {
8824 				if ( elem[ expando ] === doneName ) {
8825 					match = checkSet[elem.sizset];
8826 					break;
8827 				}
8828 
8829 				if ( elem.nodeType === 1 ) {
8830 					if ( !isXML ) {
8831 						elem[ expando ] = doneName;
8832 						elem.sizset = i;
8833 					}
8834 
8835 					if ( typeof cur !== "string" ) {
8836 						if ( elem === cur ) {
8837 							match = true;
8838 							break;
8839 						}
8840 
8841 					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
8842 						match = elem;
8843 						break;
8844 					}
8845 				}
8846 
8847 				elem = elem[dir];
8848 			}
8849 
8850 			checkSet[i] = match;
8851 		}
8852 	}
8853 }
8854 
8855 if ( document.documentElement.contains ) {
8856 	Sizzle.contains = function( a, b ) {
8857 		return a !== b && (a.contains ? a.contains(b) : true);
8858 	};
8859 
8860 } else if ( document.documentElement.compareDocumentPosition ) {
8861 	Sizzle.contains = function( a, b ) {
8862 		return !!(a.compareDocumentPosition(b) & 16);
8863 	};
8864 
8865 } else {
8866 	Sizzle.contains = function() {
8867 		return false;
8868 	};
8869 }
8870 
8871 Sizzle.isXML = function( elem ) {
8872 	// documentElement is verified for cases where it doesn't yet exist
8873 	// (such as loading iframes in IE - #4833)
8874 	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
8875 
8876 	return documentElement ? documentElement.nodeName !== "HTML" : false;
8877 };
8878 
8879 var posProcess = function( selector, context, seed ) {
8880 	var match,
8881 		tmpSet = [],
8882 		later = "",
8883 		root = context.nodeType ? [context] : context;
8884 
8885 	// Position selectors must be done after the filter
8886 	// And so must :not(positional) so we move all PSEUDOs to the end
8887 	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
8888 		later += match[0];
8889 		selector = selector.replace( Expr.match.PSEUDO, "" );
8890 	}
8891 
8892 	selector = Expr.relative[selector] ? selector + "*" : selector;
8893 
8894 	for ( var i = 0, l = root.length; i < l; i++ ) {
8895 		Sizzle( selector, root[i], tmpSet, seed );
8896 	}
8897 
8898 	return Sizzle.filter( later, tmpSet );
8899 };
8900 
8901 // EXPOSE
8902 
8903 window.tinymce.dom.Sizzle = Sizzle;
8904 
8905 })();
8906 
8907 
8908 (function(tinymce) {
8909 	tinymce.dom.Element = function(id, settings) {
8910 		var t = this, dom, el;
8911 
8912 		t.settings = settings = settings || {};
8913 		t.id = id;
8914 		t.dom = dom = settings.dom || tinymce.DOM;
8915 
8916 		// Only IE leaks DOM references, this is a lot faster
8917 		if (!tinymce.isIE)
8918 			el = dom.get(t.id);
8919 
8920 		tinymce.each(
8921 				('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
8922 				'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
8923 				'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
8924 				'isHidden,setHTML,get').split(/,/), function(k) {
8925 					t[k] = function() {
8926 						var a = [id], i;
8927 
8928 						for (i = 0; i < arguments.length; i++)
8929 							a.push(arguments[i]);
8930 
8931 						a = dom[k].apply(dom, a);
8932 						t.update(k);
8933 
8934 						return a;
8935 					};
8936 			}
8937 		);
8938 
8939 		tinymce.extend(t, {
8940 			on : function(n, f, s) {
8941 				return tinymce.dom.Event.add(t.id, n, f, s);
8942 			},
8943 
8944 			getXY : function() {
8945 				return {
8946 					x : parseInt(t.getStyle('left')),
8947 					y : parseInt(t.getStyle('top'))
8948 				};
8949 			},
8950 
8951 			getSize : function() {
8952 				var n = dom.get(t.id);
8953 
8954 				return {
8955 					w : parseInt(t.getStyle('width') || n.clientWidth),
8956 					h : parseInt(t.getStyle('height') || n.clientHeight)
8957 				};
8958 			},
8959 
8960 			moveTo : function(x, y) {
8961 				t.setStyles({left : x, top : y});
8962 			},
8963 
8964 			moveBy : function(x, y) {
8965 				var p = t.getXY();
8966 
8967 				t.moveTo(p.x + x, p.y + y);
8968 			},
8969 
8970 			resizeTo : function(w, h) {
8971 				t.setStyles({width : w, height : h});
8972 			},
8973 
8974 			resizeBy : function(w, h) {
8975 				var s = t.getSize();
8976 
8977 				t.resizeTo(s.w + w, s.h + h);
8978 			},
8979 
8980 			update : function(k) {
8981 				var b;
8982 
8983 				if (tinymce.isIE6 && settings.blocker) {
8984 					k = k || '';
8985 
8986 					// Ignore getters
8987 					if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
8988 						return;
8989 
8990 					// Remove blocker on remove
8991 					if (k == 'remove') {
8992 						dom.remove(t.blocker);
8993 						return;
8994 					}
8995 
8996 					if (!t.blocker) {
8997 						t.blocker = dom.uniqueId();
8998 						b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
8999 						dom.setStyle(b, 'opacity', 0);
9000 					} else
9001 						b = dom.get(t.blocker);
9002 
9003 					dom.setStyles(b, {
9004 						left : t.getStyle('left', 1),
9005 						top : t.getStyle('top', 1),
9006 						width : t.getStyle('width', 1),
9007 						height : t.getStyle('height', 1),
9008 						display : t.getStyle('display', 1),
9009 						zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
9010 					});
9011 				}
9012 			}
9013 		});
9014 	};
9015 })(tinymce);
9016 
9017 (function(tinymce) {
9018 	function trimNl(s) {
9019 		return s.replace(/[\n\r]+/g, '');
9020 	};
9021 
9022 	// Shorten names
9023 	var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker;
9024 
9025 	tinymce.create('tinymce.dom.Selection', {
9026 		Selection : function(dom, win, serializer, editor) {
9027 			var t = this;
9028 
9029 			t.dom = dom;
9030 			t.win = win;
9031 			t.serializer = serializer;
9032 			t.editor = editor;
9033 
9034 			// Add events
9035 			each([
9036 				'onBeforeSetContent',
9037 
9038 				'onBeforeGetContent',
9039 
9040 				'onSetContent',
9041 
9042 				'onGetContent'
9043 			], function(e) {
9044 				t[e] = new tinymce.util.Dispatcher(t);
9045 			});
9046 
9047 			// No W3C Range support
9048 			if (!t.win.getSelection)
9049 				t.tridentSel = new tinymce.dom.TridentSelection(t);
9050 
9051 			if (tinymce.isIE && dom.boxModel)
9052 				this._fixIESelection();
9053 
9054 			// Prevent leaks
9055 			tinymce.addUnload(t.destroy, t);
9056 		},
9057 
9058 		setCursorLocation: function(node, offset) {
9059 			var t = this; var r = t.dom.createRng();
9060 			r.setStart(node, offset);
9061 			r.setEnd(node, offset);
9062 			t.setRng(r);
9063 			t.collapse(false);
9064 		},
9065 		getContent : function(s) {
9066 			var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
9067 
9068 			s = s || {};
9069 			wb = wa = '';
9070 			s.get = true;
9071 			s.format = s.format || 'html';
9072 			s.forced_root_block = '';
9073 			t.onBeforeGetContent.dispatch(t, s);
9074 
9075 			if (s.format == 'text')
9076 				return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
9077 
9078 			if (r.cloneContents) {
9079 				n = r.cloneContents();
9080 
9081 				if (n)
9082 					e.appendChild(n);
9083 			} else if (is(r.item) || is(r.htmlText)) {
9084 				// IE will produce invalid markup if elements are present that
9085 				// it doesn't understand like custom elements or HTML5 elements.
9086 				// Adding a BR in front of the contents and then remoiving it seems to fix it though.
9087 				e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);
9088 				e.removeChild(e.firstChild);
9089 			} else
9090 				e.innerHTML = r.toString();
9091 
9092 			// Keep whitespace before and after
9093 			if (/^\s/.test(e.innerHTML))
9094 				wb = ' ';
9095 
9096 			if (/\s+$/.test(e.innerHTML))
9097 				wa = ' ';
9098 
9099 			s.getInner = true;
9100 
9101 			s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
9102 			t.onGetContent.dispatch(t, s);
9103 
9104 			return s.content;
9105 		},
9106 
9107 		setContent : function(content, args) {
9108 			var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
9109 
9110 			args = args || {format : 'html'};
9111 			args.set = true;
9112 			content = args.content = content;
9113 
9114 			// Dispatch before set content event
9115 			if (!args.no_events)
9116 				self.onBeforeSetContent.dispatch(self, args);
9117 
9118 			content = args.content;
9119 
9120 			if (rng.insertNode) {
9121 				// Make caret marker since insertNode places the caret in the beginning of text after insert
9122 				content += '<span id="__caret">_</span>';
9123 
9124 				// Delete and insert new node
9125 				if (rng.startContainer == doc && rng.endContainer == doc) {
9126 					// WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
9127 					doc.body.innerHTML = content;
9128 				} else {
9129 					rng.deleteContents();
9130 
9131 					if (doc.body.childNodes.length === 0) {
9132 						doc.body.innerHTML = content;
9133 					} else {
9134 						// createContextualFragment doesn't exists in IE 9 DOMRanges
9135 						if (rng.createContextualFragment) {
9136 							rng.insertNode(rng.createContextualFragment(content));
9137 						} else {
9138 							// Fake createContextualFragment call in IE 9
9139 							frag = doc.createDocumentFragment();
9140 							temp = doc.createElement('div');
9141 
9142 							frag.appendChild(temp);
9143 							temp.outerHTML = content;
9144 
9145 							rng.insertNode(frag);
9146 						}
9147 					}
9148 				}
9149 
9150 				// Move to caret marker
9151 				caretNode = self.dom.get('__caret');
9152 
9153 				// Make sure we wrap it compleatly, Opera fails with a simple select call
9154 				rng = doc.createRange();
9155 				rng.setStartBefore(caretNode);
9156 				rng.setEndBefore(caretNode);
9157 				self.setRng(rng);
9158 
9159 				// Remove the caret position
9160 				self.dom.remove('__caret');
9161 
9162 				try {
9163 					self.setRng(rng);
9164 				} catch (ex) {
9165 					// Might fail on Opera for some odd reason
9166 				}
9167 			} else {
9168 				if (rng.item) {
9169 					// Delete content and get caret text selection
9170 					doc.execCommand('Delete', false, null);
9171 					rng = self.getRng();
9172 				}
9173 
9174 				// Explorer removes spaces from the beginning of pasted contents
9175 				if (/^\s+/.test(content)) {
9176 					rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
9177 					self.dom.remove('__mce_tmp');
9178 				} else
9179 					rng.pasteHTML(content);
9180 			}
9181 
9182 			// Dispatch set content event
9183 			if (!args.no_events)
9184 				self.onSetContent.dispatch(self, args);
9185 		},
9186 
9187 		getStart : function() {
9188 			var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
9189 
9190 			if (rng.duplicate || rng.item) {
9191 				// Control selection, return first item
9192 				if (rng.item)
9193 					return rng.item(0);
9194 
9195 				// Get start element
9196 				checkRng = rng.duplicate();
9197 				checkRng.collapse(1);
9198 				startElement = checkRng.parentElement();
9199 				if (startElement.ownerDocument !== self.dom.doc) {
9200 					startElement = self.dom.getRoot();
9201 				}
9202 
9203 				// Check if range parent is inside the start element, then return the inner parent element
9204 				// This will fix issues when a single element is selected, IE would otherwise return the wrong start element
9205 				parentElement = node = rng.parentElement();
9206 				while (node = node.parentNode) {
9207 					if (node == startElement) {
9208 						startElement = parentElement;
9209 						break;
9210 					}
9211 				}
9212 
9213 				return startElement;
9214 			} else {
9215 				startElement = rng.startContainer;
9216 
9217 				if (startElement.nodeType == 1 && startElement.hasChildNodes())
9218 					startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
9219 
9220 				if (startElement && startElement.nodeType == 3)
9221 					return startElement.parentNode;
9222 
9223 				return startElement;
9224 			}
9225 		},
9226 
9227 		getEnd : function() {
9228 			var self = this, rng = self.getRng(), endElement, endOffset;
9229 
9230 			if (rng.duplicate || rng.item) {
9231 				if (rng.item)
9232 					return rng.item(0);
9233 
9234 				rng = rng.duplicate();
9235 				rng.collapse(0);
9236 				endElement = rng.parentElement();
9237 				if (endElement.ownerDocument !== self.dom.doc) {
9238 					endElement = self.dom.getRoot();
9239 				}
9240 
9241 				if (endElement && endElement.nodeName == 'BODY')
9242 					return endElement.lastChild || endElement;
9243 
9244 				return endElement;
9245 			} else {
9246 				endElement = rng.endContainer;
9247 				endOffset = rng.endOffset;
9248 
9249 				if (endElement.nodeType == 1 && endElement.hasChildNodes())
9250 					endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
9251 
9252 				if (endElement && endElement.nodeType == 3)
9253 					return endElement.parentNode;
9254 
9255 				return endElement;
9256 			}
9257 		},
9258 
9259 		getBookmark : function(type, normalized) {
9260 			var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
9261 
9262 			function findIndex(name, element) {
9263 				var index = 0;
9264 
9265 				each(dom.select(name), function(node, i) {
9266 					if (node == element)
9267 						index = i;
9268 				});
9269 
9270 				return index;
9271 			};
9272 
9273 			function normalizeTableCellSelection(rng) {
9274 				function moveEndPoint(start) {
9275 					var container, offset, childNodes, prefix = start ? 'start' : 'end';
9276 
9277 					container = rng[prefix + 'Container'];
9278 					offset = rng[prefix + 'Offset'];
9279 
9280 					if (container.nodeType == 1 && container.nodeName == "TR") {
9281 						childNodes = container.childNodes;
9282 						container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
9283 						if (container) {
9284 							offset = start ? 0 : container.childNodes.length;
9285 							rng['set' + (start ? 'Start' : 'End')](container, offset);
9286 						}
9287 					}
9288 				};
9289 
9290 				moveEndPoint(true);
9291 				moveEndPoint();
9292 
9293 				return rng;
9294 			};
9295 
9296 			function getLocation() {
9297 				var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
9298 
9299 				function getPoint(rng, start) {
9300 					var container = rng[start ? 'startContainer' : 'endContainer'],
9301 						offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
9302 
9303 					if (container.nodeType == 3) {
9304 						if (normalized) {
9305 							for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
9306 								offset += node.nodeValue.length;
9307 						}
9308 
9309 						point.push(offset);
9310 					} else {
9311 						childNodes = container.childNodes;
9312 
9313 						if (offset >= childNodes.length && childNodes.length) {
9314 							after = 1;
9315 							offset = Math.max(0, childNodes.length - 1);
9316 						}
9317 
9318 						point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
9319 					}
9320 
9321 					for (; container && container != root; container = container.parentNode)
9322 						point.push(t.dom.nodeIndex(container, normalized));
9323 
9324 					return point;
9325 				};
9326 
9327 				bookmark.start = getPoint(rng, true);
9328 
9329 				if (!t.isCollapsed())
9330 					bookmark.end = getPoint(rng);
9331 
9332 				return bookmark;
9333 			};
9334 
9335 			if (type == 2) {
9336 				if (t.tridentSel)
9337 					return t.tridentSel.getBookmark(type);
9338 
9339 				return getLocation();
9340 			}
9341 
9342 			// Handle simple range
9343 			if (type)
9344 				return {rng : t.getRng()};
9345 
9346 			rng = t.getRng();
9347 			id = dom.uniqueId();
9348 			collapsed = tinyMCE.activeEditor.selection.isCollapsed();
9349 			styles = 'overflow:hidden;line-height:0px';
9350 
9351 			// Explorer method
9352 			if (rng.duplicate || rng.item) {
9353 				// Text selection
9354 				if (!rng.item) {
9355 					rng2 = rng.duplicate();
9356 
9357 					try {
9358 						// Insert start marker
9359 						rng.collapse();
9360 						rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
9361 
9362 						// Insert end marker
9363 						if (!collapsed) {
9364 							rng2.collapse(false);
9365 
9366 							// Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
9367 							rng.moveToElementText(rng2.parentElement());
9368 							if (rng.compareEndPoints('StartToEnd', rng2) === 0)
9369 								rng2.move('character', -1);
9370 
9371 							rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
9372 						}
9373 					} catch (ex) {
9374 						// IE might throw unspecified error so lets ignore it
9375 						return null;
9376 					}
9377 				} else {
9378 					// Control selection
9379 					element = rng.item(0);
9380 					name = element.nodeName;
9381 
9382 					return {name : name, index : findIndex(name, element)};
9383 				}
9384 			} else {
9385 				element = t.getNode();
9386 				name = element.nodeName;
9387 				if (name == 'IMG')
9388 					return {name : name, index : findIndex(name, element)};
9389 
9390 				// W3C method
9391 				rng2 = normalizeTableCellSelection(rng.cloneRange());
9392 
9393 				// Insert end marker
9394 				if (!collapsed) {
9395 					rng2.collapse(false);
9396 					rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
9397 				}
9398 
9399 				rng = normalizeTableCellSelection(rng);
9400 				rng.collapse(true);
9401 				rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
9402 			}
9403 
9404 			t.moveToBookmark({id : id, keep : 1});
9405 
9406 			return {id : id};
9407 		},
9408 
9409 		moveToBookmark : function(bookmark) {
9410 			var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
9411 
9412 			function setEndPoint(start) {
9413 				var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
9414 
9415 				if (point) {
9416 					offset = point[0];
9417 
9418 					// Find container node
9419 					for (node = root, i = point.length - 1; i >= 1; i--) {
9420 						children = node.childNodes;
9421 
9422 						if (point[i] > children.length - 1)
9423 							return;
9424 
9425 						node = children[point[i]];
9426 					}
9427 
9428 					// Move text offset to best suitable location
9429 					if (node.nodeType === 3)
9430 						offset = Math.min(point[0], node.nodeValue.length);
9431 
9432 					// Move element offset to best suitable location
9433 					if (node.nodeType === 1)
9434 						offset = Math.min(point[0], node.childNodes.length);
9435 
9436 					// Set offset within container node
9437 					if (start)
9438 						rng.setStart(node, offset);
9439 					else
9440 						rng.setEnd(node, offset);
9441 				}
9442 
9443 				return true;
9444 			};
9445 
9446 			function restoreEndPoint(suffix) {
9447 				var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
9448 
9449 				if (marker) {
9450 					node = marker.parentNode;
9451 
9452 					if (suffix == 'start') {
9453 						if (!keep) {
9454 							idx = dom.nodeIndex(marker);
9455 						} else {
9456 							node = marker.firstChild;
9457 							idx = 1;
9458 						}
9459 
9460 						startContainer = endContainer = node;
9461 						startOffset = endOffset = idx;
9462 					} else {
9463 						if (!keep) {
9464 							idx = dom.nodeIndex(marker);
9465 						} else {
9466 							node = marker.firstChild;
9467 							idx = 1;
9468 						}
9469 
9470 						endContainer = node;
9471 						endOffset = idx;
9472 					}
9473 
9474 					if (!keep) {
9475 						prev = marker.previousSibling;
9476 						next = marker.nextSibling;
9477 
9478 						// Remove all marker text nodes
9479 						each(tinymce.grep(marker.childNodes), function(node) {
9480 							if (node.nodeType == 3)
9481 								node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
9482 						});
9483 
9484 						// Remove marker but keep children if for example contents where inserted into the marker
9485 						// Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
9486 						while (marker = dom.get(bookmark.id + '_' + suffix))
9487 							dom.remove(marker, 1);
9488 
9489 						// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
9490 						// and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
9491 						if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
9492 							idx = prev.nodeValue.length;
9493 							prev.appendData(next.nodeValue);
9494 							dom.remove(next);
9495 
9496 							if (suffix == 'start') {
9497 								startContainer = endContainer = prev;
9498 								startOffset = endOffset = idx;
9499 							} else {
9500 								endContainer = prev;
9501 								endOffset = idx;
9502 							}
9503 						}
9504 					}
9505 				}
9506 			};
9507 
9508 			function addBogus(node) {
9509 				// Adds a bogus BR element for empty block elements
9510 				if (dom.isBlock(node) && !node.innerHTML && !isIE)
9511 					node.innerHTML = '<br data-mce-bogus="1" />';
9512 
9513 				return node;
9514 			};
9515 
9516 			if (bookmark) {
9517 				if (bookmark.start) {
9518 					rng = dom.createRng();
9519 					root = dom.getRoot();
9520 
9521 					if (t.tridentSel)
9522 						return t.tridentSel.moveToBookmark(bookmark);
9523 
9524 					if (setEndPoint(true) && setEndPoint()) {
9525 						t.setRng(rng);
9526 					}
9527 				} else if (bookmark.id) {
9528 					// Restore start/end points
9529 					restoreEndPoint('start');
9530 					restoreEndPoint('end');
9531 
9532 					if (startContainer) {
9533 						rng = dom.createRng();
9534 						rng.setStart(addBogus(startContainer), startOffset);
9535 						rng.setEnd(addBogus(endContainer), endOffset);
9536 						t.setRng(rng);
9537 					}
9538 				} else if (bookmark.name) {
9539 					t.select(dom.select(bookmark.name)[bookmark.index]);
9540 				} else if (bookmark.rng)
9541 					t.setRng(bookmark.rng);
9542 			}
9543 		},
9544 
9545 		select : function(node, content) {
9546 			var t = this, dom = t.dom, rng = dom.createRng(), idx;
9547 
9548 			function setPoint(node, start) {
9549 				var walker = new TreeWalker(node, node);
9550 
9551 				do {
9552 					// Text node
9553 					if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) {
9554 						if (start)
9555 							rng.setStart(node, 0);
9556 						else
9557 							rng.setEnd(node, node.nodeValue.length);
9558 
9559 						return;
9560 					}
9561 
9562 					// BR element
9563 					if (node.nodeName == 'BR') {
9564 						if (start)
9565 							rng.setStartBefore(node);
9566 						else
9567 							rng.setEndBefore(node);
9568 
9569 						return;
9570 					}
9571 				} while (node = (start ? walker.next() : walker.prev()));
9572 			};
9573 
9574 			if (node) {
9575 				idx = dom.nodeIndex(node);
9576 				rng.setStart(node.parentNode, idx);
9577 				rng.setEnd(node.parentNode, idx + 1);
9578 
9579 				// Find first/last text node or BR element
9580 				if (content) {
9581 					setPoint(node, 1);
9582 					setPoint(node);
9583 				}
9584 
9585 				t.setRng(rng);
9586 			}
9587 
9588 			return node;
9589 		},
9590 
9591 		isCollapsed : function() {
9592 			var t = this, r = t.getRng(), s = t.getSel();
9593 
9594 			if (!r || r.item)
9595 				return false;
9596 
9597 			if (r.compareEndPoints)
9598 				return r.compareEndPoints('StartToEnd', r) === 0;
9599 
9600 			return !s || r.collapsed;
9601 		},
9602 
9603 		collapse : function(to_start) {
9604 			var self = this, rng = self.getRng(), node;
9605 
9606 			// Control range on IE
9607 			if (rng.item) {
9608 				node = rng.item(0);
9609 				rng = self.win.document.body.createTextRange();
9610 				rng.moveToElementText(node);
9611 			}
9612 
9613 			rng.collapse(!!to_start);
9614 			self.setRng(rng);
9615 		},
9616 
9617 		getSel : function() {
9618 			var t = this, w = this.win;
9619 
9620 			return w.getSelection ? w.getSelection() : w.document.selection;
9621 		},
9622 
9623 		getRng : function(w3c) {
9624 			var self = this, selection, rng, elm, doc = self.win.document;
9625 
9626 			// Found tridentSel object then we need to use that one
9627 			if (w3c && self.tridentSel) {
9628 				return self.tridentSel.getRangeAt(0);
9629 			}
9630 
9631 			try {
9632 				if (selection = self.getSel()) {
9633 					rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange());
9634 				}
9635 			} catch (ex) {
9636 				// IE throws unspecified error here if TinyMCE is placed in a frame/iframe
9637 			}
9638 
9639 			// We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
9640 			if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) {
9641 				elm = doc.selection.createRange().item(0);
9642 				rng = doc.createRange();
9643 				rng.setStartBefore(elm);
9644 				rng.setEndAfter(elm);
9645 			}
9646 
9647 			// No range found then create an empty one
9648 			// This can occur when the editor is placed in a hidden container element on Gecko
9649 			// Or on IE when there was an exception
9650 			if (!rng) {
9651 				rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
9652 			}
9653 
9654 			// If range is at start of document then move it to start of body
9655 			if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
9656 				elm = self.dom.getRoot();
9657 				rng.setStart(elm, 0);
9658 				rng.setEnd(elm, 0);
9659 			}
9660 
9661 			if (self.selectedRange && self.explicitRange) {
9662 				if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {
9663 					// Safari, Opera and Chrome only ever select text which causes the range to change.
9664 					// This lets us use the originally set range if the selection hasn't been changed by the user.
9665 					rng = self.explicitRange;
9666 				} else {
9667 					self.selectedRange = null;
9668 					self.explicitRange = null;
9669 				}
9670 			}
9671 
9672 			return rng;
9673 		},
9674 
9675 		setRng : function(r, forward) {
9676 			var s, t = this;
9677 
9678 			if (!t.tridentSel) {
9679 				s = t.getSel();
9680 
9681 				if (s) {
9682 					t.explicitRange = r;
9683 
9684 					try {
9685 						s.removeAllRanges();
9686 					} catch (ex) {
9687 						// IE9 might throw errors here don't know why
9688 					}
9689 
9690 					s.addRange(r);
9691 
9692 					// Forward is set to false and we have an extend function
9693 					if (forward === false && s.extend) {
9694 						s.collapse(r.endContainer, r.endOffset);
9695 						s.extend(r.startContainer, r.startOffset);
9696 					}
9697 
9698 					// adding range isn't always successful so we need to check range count otherwise an exception can occur
9699 					t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;
9700 				}
9701 			} else {
9702 				// Is W3C Range
9703 				if (r.cloneRange) {
9704 					try {
9705 						t.tridentSel.addRange(r);
9706 						return;
9707 					} catch (ex) {
9708 						//IE9 throws an error here if called before selection is placed in the editor
9709 					}
9710 				}
9711 
9712 				// Is IE specific range
9713 				try {
9714 					r.select();
9715 				} catch (ex) {
9716 					// Needed for some odd IE bug #1843306
9717 				}
9718 			}
9719 		},
9720 
9721 		setNode : function(n) {
9722 			var t = this;
9723 
9724 			t.setContent(t.dom.getOuterHTML(n));
9725 
9726 			return n;
9727 		},
9728 
9729 		getNode : function() {
9730 			var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
9731 
9732 			function skipEmptyTextNodes(n, forwards) {
9733 				var orig = n;
9734 				while (n && n.nodeType === 3 && n.length === 0) {
9735 					n = forwards ? n.nextSibling : n.previousSibling;
9736 				}
9737 				return n || orig;
9738 			};
9739 
9740 			// Range maybe lost after the editor is made visible again
9741 			if (!rng)
9742 				return t.dom.getRoot();
9743 
9744 			if (rng.setStart) {
9745 				elm = rng.commonAncestorContainer;
9746 
9747 				// Handle selection a image or other control like element such as anchors
9748 				if (!rng.collapsed) {
9749 					if (rng.startContainer == rng.endContainer) {
9750 						if (rng.endOffset - rng.startOffset < 2) {
9751 							if (rng.startContainer.hasChildNodes())
9752 								elm = rng.startContainer.childNodes[rng.startOffset];
9753 						}
9754 					}
9755 
9756 					// If the anchor node is a element instead of a text node then return this element
9757 					//if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
9758 					//	return sel.anchorNode.childNodes[sel.anchorOffset];
9759 
9760 					// Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
9761 					// This happens when you double click an underlined word in FireFox.
9762 					if (start.nodeType === 3 && end.nodeType === 3) {
9763 						if (start.length === rng.startOffset) {
9764 							start = skipEmptyTextNodes(start.nextSibling, true);
9765 						} else {
9766 							start = start.parentNode;
9767 						}
9768 						if (rng.endOffset === 0) {
9769 							end = skipEmptyTextNodes(end.previousSibling, false);
9770 						} else {
9771 							end = end.parentNode;
9772 						}
9773 
9774 						if (start && start === end)
9775 							return start;
9776 					}
9777 				}
9778 
9779 				if (elm && elm.nodeType == 3)
9780 					return elm.parentNode;
9781 
9782 				return elm;
9783 			}
9784 
9785 			return rng.item ? rng.item(0) : rng.parentElement();
9786 		},
9787 
9788 		getSelectedBlocks : function(st, en) {
9789 			var t = this, dom = t.dom, sb, eb, n, bl = [];
9790 
9791 			sb = dom.getParent(st || t.getStart(), dom.isBlock);
9792 			eb = dom.getParent(en || t.getEnd(), dom.isBlock);
9793 
9794 			if (sb)
9795 				bl.push(sb);
9796 
9797 			if (sb && eb && sb != eb) {
9798 				n = sb;
9799 
9800 				var walker = new TreeWalker(sb, dom.getRoot());
9801 				while ((n = walker.next()) && n != eb) {
9802 					if (dom.isBlock(n))
9803 						bl.push(n);
9804 				}
9805 			}
9806 
9807 			if (eb && sb != eb)
9808 				bl.push(eb);
9809 
9810 			return bl;
9811 		},
9812 
9813 		isForward: function(){
9814 			var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
9815 
9816 			// No support for selection direction then always return true
9817 			if (!sel || sel.anchorNode == null || sel.focusNode == null) {
9818 				return true;
9819 			}
9820 
9821 			anchorRange = dom.createRng();
9822 			anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
9823 			anchorRange.collapse(true);
9824 
9825 			focusRange = dom.createRng();
9826 			focusRange.setStart(sel.focusNode, sel.focusOffset);
9827 			focusRange.collapse(true);
9828 
9829 			return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
9830 		},
9831 
9832 		normalize : function() {
9833 			var self = this, rng, normalized, collapsed, node, sibling;
9834 
9835 			function normalizeEndPoint(start) {
9836 				var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
9837 
9838 				function hasBrBeforeAfter(node, left) {
9839 					var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
9840 
9841 					while (node = walker[left ? 'prev' : 'next']()) {
9842 						if (node.nodeName === "BR") {
9843 							return true;
9844 						}
9845 					}
9846 				};
9847 
9848 				// Walks the dom left/right to find a suitable text node to move the endpoint into
9849 				// It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
9850 				function findTextNodeRelative(left, startNode) {
9851 					var walker, lastInlineElement;
9852 
9853 					startNode = startNode || container;
9854 					walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);
9855 
9856 					// Walk left until we hit a text node we can move to or a block/br/img
9857 					while (node = walker[left ? 'prev' : 'next']()) {
9858 						// Found text node that has a length
9859 						if (node.nodeType === 3 && node.nodeValue.length > 0) {
9860 							container = node;
9861 							offset = left ? node.nodeValue.length : 0;
9862 							normalized = true;
9863 							return;
9864 						}
9865 
9866 						// Break if we find a block or a BR/IMG/INPUT etc
9867 						if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
9868 							return;
9869 						}
9870 
9871 						lastInlineElement = node;
9872 					}
9873 
9874 					// Only fetch the last inline element when in caret mode for now
9875 					if (collapsed && lastInlineElement) {
9876 						container = lastInlineElement;
9877 						normalized = true;
9878 						offset = 0;
9879 					}
9880 				};
9881 
9882 				container = rng[(start ? 'start' : 'end') + 'Container'];
9883 				offset = rng[(start ? 'start' : 'end') + 'Offset'];
9884 				nonEmptyElementsMap = dom.schema.getNonEmptyElements();
9885 
9886 				// If the container is a document move it to the body element
9887 				if (container.nodeType === 9) {
9888 					container = dom.getRoot();
9889 					offset = 0;
9890 				}
9891 
9892 				// If the container is body try move it into the closest text node or position
9893 				if (container === body) {
9894 					// If start is before/after a image, table etc
9895 					if (start) {
9896 						node = container.childNodes[offset > 0 ? offset - 1 : 0];
9897 						if (node) {
9898 							nodeName = node.nodeName.toLowerCase();
9899 							if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
9900 								return;
9901 							}
9902 						}
9903 					}
9904 
9905 					// Resolve the index
9906 					if (container.hasChildNodes()) {
9907 						container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
9908 						offset = 0;
9909 
9910 						// Don't walk into elements that doesn't have any child nodes like a IMG
9911 						if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
9912 							// Walk the DOM to find a text node to place the caret at or a BR
9913 							node = container;
9914 							walker = new TreeWalker(container, body);
9915 
9916 							do {
9917 								// Found a text node use that position
9918 								if (node.nodeType === 3 && node.nodeValue.length > 0) {
9919 									offset = start ? 0 : node.nodeValue.length;
9920 									container = node;
9921 									normalized = true;
9922 									break;
9923 								}
9924 
9925 								// Found a BR/IMG element that we can place the caret before
9926 								if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
9927 									offset = dom.nodeIndex(node);
9928 									container = node.parentNode;
9929 
9930 									// Put caret after image when moving the end point
9931 									if (node.nodeName ==  "IMG" && !start) {
9932 										offset++;
9933 									}
9934 
9935 									normalized = true;
9936 									break;
9937 								}
9938 							} while (node = (start ? walker.next() : walker.prev()));
9939 						}
9940 					}
9941 				}
9942 
9943 				// Lean the caret to the left if possible
9944 				if (collapsed) {
9945 					// So this: <b>x</b><i>|x</i>
9946 					// Becomes: <b>x|</b><i>x</i>
9947 					// Seems that only gecko has issues with this
9948 					if (container.nodeType === 3 && offset === 0) {
9949 						findTextNodeRelative(true);
9950 					}
9951 
9952 					// Lean left into empty inline elements when the caret is before a BR
9953 					// So this: <i><b></b><i>|<br></i>
9954 					// Becomes: <i><b>|</b><i><br></i>
9955 					// Seems that only gecko has issues with this
9956 					if (container.nodeType === 1) {
9957 						node = container.childNodes[offset];
9958 						if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
9959 							findTextNodeRelative(true, container.childNodes[offset]);
9960 						}
9961 					}
9962 				}
9963 
9964 				// Lean the start of the selection right if possible
9965 				// So this: x[<b>x]</b>
9966 				// Becomes: x<b>[x]</b>
9967 				if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
9968 					findTextNodeRelative(false);
9969 				}
9970 
9971 				// Set endpoint if it was normalized
9972 				if (normalized)
9973 					rng['set' + (start ? 'Start' : 'End')](container, offset);
9974 			};
9975 
9976 			// Normalize only on non IE browsers for now
9977 			if (tinymce.isIE)
9978 				return;
9979 			
9980 			rng = self.getRng();
9981 			collapsed = rng.collapsed;
9982 
9983 			// Normalize the end points
9984 			normalizeEndPoint(true);
9985 
9986 			if (!collapsed)
9987 				normalizeEndPoint();
9988 
9989 			// Set the selection if it was normalized
9990 			if (normalized) {
9991 				// If it was collapsed then make sure it still is
9992 				if (collapsed) {
9993 					rng.collapse(true);
9994 				}
9995 
9996 				//console.log(self.dom.dumpRng(rng));
9997 				self.setRng(rng, self.isForward());
9998 			}
9999 		},
10000 
10001 		selectorChanged: function(selector, callback) {
10002 			var self = this, currentSelectors;
10003 
10004 			if (!self.selectorChangedData) {
10005 				self.selectorChangedData = {};
10006 				currentSelectors = {};
10007 
10008 				self.editor.onNodeChange.addToTop(function(ed, cm, node) {
10009 					var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
10010 
10011 					// Check for new matching selectors
10012 					each(self.selectorChangedData, function(callbacks, selector) {
10013 						each(parents, function(node) {
10014 							if (dom.is(node, selector)) {
10015 								if (!currentSelectors[selector]) {
10016 									// Execute callbacks
10017 									each(callbacks, function(callback) {
10018 										callback(true, {node: node, selector: selector, parents: parents});
10019 									});
10020 
10021 									currentSelectors[selector] = callbacks;
10022 								}
10023 
10024 								matchedSelectors[selector] = callbacks;
10025 								return false;
10026 							}
10027 						});
10028 					});
10029 
10030 					// Check if current selectors still match
10031 					each(currentSelectors, function(callbacks, selector) {
10032 						if (!matchedSelectors[selector]) {
10033 							delete currentSelectors[selector];
10034 
10035 							each(callbacks, function(callback) {
10036 								callback(false, {node: node, selector: selector, parents: parents});
10037 							});
10038 						}
10039 					});
10040 				});
10041 			}
10042 
10043 			// Add selector listeners
10044 			if (!self.selectorChangedData[selector]) {
10045 				self.selectorChangedData[selector] = [];
10046 			}
10047 
10048 			self.selectorChangedData[selector].push(callback);
10049 
10050 			return self;
10051 		},
10052 
10053 		destroy : function(manual) {
10054 			var self = this;
10055 
10056 			self.win = null;
10057 
10058 			// Manual destroy then remove unload handler
10059 			if (!manual)
10060 				tinymce.removeUnload(self.destroy);
10061 		},
10062 
10063 		// IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
10064 		_fixIESelection : function() {
10065 			var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
10066 
10067 			// Return range from point or null if it failed
10068 			function rngFromPoint(x, y) {
10069 				var rng = body.createTextRange();
10070 
10071 				try {
10072 					rng.moveToPoint(x, y);
10073 				} catch (ex) {
10074 					// IE sometimes throws and exception, so lets just ignore it
10075 					rng = null;
10076 				}
10077 
10078 				return rng;
10079 			};
10080 
10081 			// Fires while the selection is changing
10082 			function selectionChange(e) {
10083 				var pointRng;
10084 
10085 				// Check if the button is down or not
10086 				if (e.button) {
10087 					// Create range from mouse position
10088 					pointRng = rngFromPoint(e.x, e.y);
10089 
10090 					if (pointRng) {
10091 						// Check if pointRange is before/after selection then change the endPoint
10092 						if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
10093 							pointRng.setEndPoint('StartToStart', startRng);
10094 						else
10095 							pointRng.setEndPoint('EndToEnd', startRng);
10096 
10097 						pointRng.select();
10098 					}
10099 				} else
10100 					endSelection();
10101 			}
10102 
10103 			// Removes listeners
10104 			function endSelection() {
10105 				var rng = doc.selection.createRange();
10106 
10107 				// If the range is collapsed then use the last start range
10108 				if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
10109 					startRng.select();
10110 
10111 				dom.unbind(doc, 'mouseup', endSelection);
10112 				dom.unbind(doc, 'mousemove', selectionChange);
10113 				startRng = started = 0;
10114 			};
10115 
10116 			// Make HTML element unselectable since we are going to handle selection by hand
10117 			doc.documentElement.unselectable = true;
10118 			
10119 			// Detect when user selects outside BODY
10120 			dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
10121 				if (e.target.nodeName === 'HTML') {
10122 					if (started)
10123 						endSelection();
10124 
10125 					// Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
10126 					htmlElm = doc.documentElement;
10127 					if (htmlElm.scrollHeight > htmlElm.clientHeight)
10128 						return;
10129 
10130 					started = 1;
10131 					// Setup start position
10132 					startRng = rngFromPoint(e.x, e.y);
10133 					if (startRng) {
10134 						// Listen for selection change events
10135 						dom.bind(doc, 'mouseup', endSelection);
10136 						dom.bind(doc, 'mousemove', selectionChange);
10137 
10138 						dom.win.focus();
10139 						startRng.select();
10140 					}
10141 				}
10142 			});
10143 		}
10144 	});
10145 })(tinymce);
10146 
10147 (function(tinymce) {
10148 	tinymce.dom.Serializer = function(settings, dom, schema) {
10149 		var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
10150 
10151 		// Support the old apply_source_formatting option
10152 		if (!settings.apply_source_formatting)
10153 			settings.indent = false;
10154 
10155 		// Default DOM and Schema if they are undefined
10156 		dom = dom || tinymce.DOM;
10157 		schema = schema || new tinymce.html.Schema(settings);
10158 		settings.entity_encoding = settings.entity_encoding || 'named';
10159 		settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
10160 
10161 		onPreProcess = new tinymce.util.Dispatcher(self);
10162 
10163 		onPostProcess = new tinymce.util.Dispatcher(self);
10164 
10165 		htmlParser = new tinymce.html.DomParser(settings, schema);
10166 
10167 		// Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
10168 		htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
10169 			var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
10170 
10171 			while (i--) {
10172 				node = nodes[i];
10173 
10174 				value = node.attributes.map[internalName];
10175 				if (value !== undef) {
10176 					// Set external name to internal value and remove internal
10177 					node.attr(name, value.length > 0 ? value : null);
10178 					node.attr(internalName, null);
10179 				} else {
10180 					// No internal attribute found then convert the value we have in the DOM
10181 					value = node.attributes.map[name];
10182 
10183 					if (name === "style")
10184 						value = dom.serializeStyle(dom.parseStyle(value), node.name);
10185 					else if (urlConverter)
10186 						value = urlConverter.call(urlConverterScope, value, name, node.name);
10187 
10188 					node.attr(name, value.length > 0 ? value : null);
10189 				}
10190 			}
10191 		});
10192 
10193 		// Remove internal classes mceItem<..> or mceSelected
10194 		htmlParser.addAttributeFilter('class', function(nodes, name) {
10195 			var i = nodes.length, node, value;
10196 
10197 			while (i--) {
10198 				node = nodes[i];
10199 				value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, '');
10200 				node.attr('class', value.length > 0 ? value : null);
10201 			}
10202 		});
10203 
10204 		// Remove bookmark elements
10205 		htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
10206 			var i = nodes.length, node;
10207 
10208 			while (i--) {
10209 				node = nodes[i];
10210 
10211 				if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
10212 					node.remove();
10213 			}
10214 		});
10215 
10216 		// Remove expando attributes
10217 		htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) {
10218 			var i = nodes.length;
10219 
10220 			while (i--) {
10221 				nodes[i].attr(name, null);
10222 			}
10223 		});
10224 
10225 		// Force script into CDATA sections and remove the mce- prefix also add comments around styles
10226 		htmlParser.addNodeFilter('script,style', function(nodes, name) {
10227 			var i = nodes.length, node, value;
10228 
10229 			function trim(value) {
10230 				return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
10231 						.replace(/^[\r\n]*|[\r\n]*$/g, '')
10232 						.replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
10233 						.replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
10234 			};
10235 
10236 			while (i--) {
10237 				node = nodes[i];
10238 				value = node.firstChild ? node.firstChild.value : '';
10239 
10240 				if (name === "script") {
10241 					// Remove mce- prefix from script elements
10242 					node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
10243 
10244 					if (value.length > 0)
10245 						node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
10246 				} else {
10247 					if (value.length > 0)
10248 						node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
10249 				}
10250 			}
10251 		});
10252 
10253 		// Convert comments to cdata and handle protected comments
10254 		htmlParser.addNodeFilter('#comment', function(nodes, name) {
10255 			var i = nodes.length, node;
10256 
10257 			while (i--) {
10258 				node = nodes[i];
10259 
10260 				if (node.value.indexOf('[CDATA[') === 0) {
10261 					node.name = '#cdata';
10262 					node.type = 4;
10263 					node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
10264 				} else if (node.value.indexOf('mce:protected ') === 0) {
10265 					node.name = "#text";
10266 					node.type = 3;
10267 					node.raw = true;
10268 					node.value = unescape(node.value).substr(14);
10269 				}
10270 			}
10271 		});
10272 
10273 		htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
10274 			var i = nodes.length, node;
10275 
10276 			while (i--) {
10277 				node = nodes[i];
10278 				if (node.type === 7)
10279 					node.remove();
10280 				else if (node.type === 1) {
10281 					if (name === "input" && !("type" in node.attributes.map))
10282 						node.attr('type', 'text');
10283 				}
10284 			}
10285 		});
10286 
10287 		// Fix list elements, TODO: Replace this later
10288 		if (settings.fix_list_elements) {
10289 			htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
10290 				var i = nodes.length, node, parentNode;
10291 
10292 				while (i--) {
10293 					node = nodes[i];
10294 					parentNode = node.parent;
10295 
10296 					if (parentNode.name === 'ul' || parentNode.name === 'ol') {
10297 						if (node.prev && node.prev.name === 'li') {
10298 							node.prev.append(node);
10299 						}
10300 					}
10301 				}
10302 			});
10303 		}
10304 
10305 		// Remove internal data attributes
10306 		htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
10307 			var i = nodes.length;
10308 
10309 			while (i--) {
10310 				nodes[i].attr(name, null);
10311 			}
10312 		});
10313 
10314 		// Return public methods
10315 		return {
10316 			schema : schema,
10317 
10318 			addNodeFilter : htmlParser.addNodeFilter,
10319 
10320 			addAttributeFilter : htmlParser.addAttributeFilter,
10321 
10322 			onPreProcess : onPreProcess,
10323 
10324 			onPostProcess : onPostProcess,
10325 
10326 			serialize : function(node, args) {
10327 				var impl, doc, oldDoc, htmlSerializer, content;
10328 
10329 				// Explorer won't clone contents of script and style and the
10330 				// selected index of select elements are cleared on a clone operation.
10331 				if (isIE && dom.select('script,style,select,map').length > 0) {
10332 					content = node.innerHTML;
10333 					node = node.cloneNode(false);
10334 					dom.setHTML(node, content);
10335 				} else
10336 					node = node.cloneNode(true);
10337 
10338 				// Nodes needs to be attached to something in WebKit/Opera
10339 				// Older builds of Opera crashes if you attach the node to an document created dynamically
10340 				// and since we can't feature detect a crash we need to sniff the acutal build number
10341 				// This fix will make DOM ranges and make Sizzle happy!
10342 				impl = node.ownerDocument.implementation;
10343 				if (impl.createHTMLDocument) {
10344 					// Create an empty HTML document
10345 					doc = impl.createHTMLDocument("");
10346 
10347 					// Add the element or it's children if it's a body element to the new document
10348 					each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
10349 						doc.body.appendChild(doc.importNode(node, true));
10350 					});
10351 
10352 					// Grab first child or body element for serialization
10353 					if (node.nodeName != 'BODY')
10354 						node = doc.body.firstChild;
10355 					else
10356 						node = doc.body;
10357 
10358 					// set the new document in DOMUtils so createElement etc works
10359 					oldDoc = dom.doc;
10360 					dom.doc = doc;
10361 				}
10362 
10363 				args = args || {};
10364 				args.format = args.format || 'html';
10365 
10366 				// Pre process
10367 				if (!args.no_events) {
10368 					args.node = node;
10369 					onPreProcess.dispatch(self, args);
10370 				}
10371 
10372 				// Setup serializer
10373 				htmlSerializer = new tinymce.html.Serializer(settings, schema);
10374 
10375 				// Parse and serialize HTML
10376 				args.content = htmlSerializer.serialize(
10377 					htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
10378 				);
10379 
10380 				// Replace all BOM characters for now until we can find a better solution
10381 				if (!args.cleanup)
10382 					args.content = args.content.replace(/\uFEFF|\u200B/g, '');
10383 
10384 				// Post process
10385 				if (!args.no_events)
10386 					onPostProcess.dispatch(self, args);
10387 
10388 				// Restore the old document if it was changed
10389 				if (oldDoc)
10390 					dom.doc = oldDoc;
10391 
10392 				args.node = null;
10393 
10394 				return args.content;
10395 			},
10396 
10397 			addRules : function(rules) {
10398 				schema.addValidElements(rules);
10399 			},
10400 
10401 			setRules : function(rules) {
10402 				schema.setValidElements(rules);
10403 			}
10404 		};
10405 	};
10406 })(tinymce);
10407 (function(tinymce) {
10408 	tinymce.dom.ScriptLoader = function(settings) {
10409 		var QUEUED = 0,
10410 			LOADING = 1,
10411 			LOADED = 2,
10412 			states = {},
10413 			queue = [],
10414 			scriptLoadedCallbacks = {},
10415 			queueLoadedCallbacks = [],
10416 			loading = 0,
10417 			undef;
10418 
10419 		function loadScript(url, callback) {
10420 			var t = this, dom = tinymce.DOM, elm, uri, loc, id;
10421 
10422 			// Execute callback when script is loaded
10423 			function done() {
10424 				dom.remove(id);
10425 
10426 				if (elm)
10427 					elm.onreadystatechange = elm.onload = elm = null;
10428 
10429 				callback();
10430 			};
10431 			
10432 			function error() {
10433 				// Report the error so it's easier for people to spot loading errors
10434 				if (typeof(console) !== "undefined" && console.log)
10435 					console.log("Failed to load: " + url);
10436 
10437 				// We can't mark it as done if there is a load error since
10438 				// A) We don't want to produce 404 errors on the server and
10439 				// B) the onerror event won't fire on all browsers.
10440 				// done();
10441 			};
10442 
10443 			id = dom.uniqueId();
10444 
10445 			if (tinymce.isIE6) {
10446 				uri = new tinymce.util.URI(url);
10447 				loc = location;
10448 
10449 				// If script is from same domain and we
10450 				// use IE 6 then use XHR since it's more reliable
10451 				if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
10452 					tinymce.util.XHR.send({
10453 						url : tinymce._addVer(uri.getURI()),
10454 						success : function(content) {
10455 							// Create new temp script element
10456 							var script = dom.create('script', {
10457 								type : 'text/javascript'
10458 							});
10459 
10460 							// Evaluate script in global scope
10461 							script.text = content;
10462 							document.getElementsByTagName('head')[0].appendChild(script);
10463 							dom.remove(script);
10464 
10465 							done();
10466 						},
10467 						
10468 						error : error
10469 					});
10470 
10471 					return;
10472 				}
10473 			}
10474 
10475 			// Create new script element
10476 			elm = document.createElement('script');
10477 			elm.id = id;
10478 			elm.type = 'text/javascript';
10479 			elm.src = tinymce._addVer(url);
10480 
10481 			// Add onload listener for non IE browsers since IE9
10482 			// fires onload event before the script is parsed and executed
10483 			if (!tinymce.isIE)
10484 				elm.onload = done;
10485 
10486 			// Add onerror event will get fired on some browsers but not all of them
10487 			elm.onerror = error;
10488 
10489 			// Opera 9.60 doesn't seem to fire the onreadystate event at correctly
10490 			if (!tinymce.isOpera) {
10491 				elm.onreadystatechange = function() {
10492 					var state = elm.readyState;
10493 
10494 					// Loaded state is passed on IE 6 however there
10495 					// are known issues with this method but we can't use
10496 					// XHR in a cross domain loading
10497 					if (state == 'complete' || state == 'loaded')
10498 						done();
10499 				};
10500 			}
10501 
10502 			// Most browsers support this feature so we report errors
10503 			// for those at least to help users track their missing plugins etc
10504 			// todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
10505 			/*elm.onerror = function() {
10506 				alert('Failed to load: ' + url);
10507 			};*/
10508 
10509 			// Add script to document
10510 			(document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
10511 		};
10512 
10513 		this.isDone = function(url) {
10514 			return states[url] == LOADED;
10515 		};
10516 
10517 		this.markDone = function(url) {
10518 			states[url] = LOADED;
10519 		};
10520 
10521 		this.add = this.load = function(url, callback, scope) {
10522 			var item, state = states[url];
10523 
10524 			// Add url to load queue
10525 			if (state == undef) {
10526 				queue.push(url);
10527 				states[url] = QUEUED;
10528 			}
10529 
10530 			if (callback) {
10531 				// Store away callback for later execution
10532 				if (!scriptLoadedCallbacks[url])
10533 					scriptLoadedCallbacks[url] = [];
10534 
10535 				scriptLoadedCallbacks[url].push({
10536 					func : callback,
10537 					scope : scope || this
10538 				});
10539 			}
10540 		};
10541 
10542 		this.loadQueue = function(callback, scope) {
10543 			this.loadScripts(queue, callback, scope);
10544 		};
10545 
10546 		this.loadScripts = function(scripts, callback, scope) {
10547 			var loadScripts;
10548 
10549 			function execScriptLoadedCallbacks(url) {
10550 				// Execute URL callback functions
10551 				tinymce.each(scriptLoadedCallbacks[url], function(callback) {
10552 					callback.func.call(callback.scope);
10553 				});
10554 
10555 				scriptLoadedCallbacks[url] = undef;
10556 			};
10557 
10558 			queueLoadedCallbacks.push({
10559 				func : callback,
10560 				scope : scope || this
10561 			});
10562 
10563 			loadScripts = function() {
10564 				var loadingScripts = tinymce.grep(scripts);
10565 
10566 				// Current scripts has been handled
10567 				scripts.length = 0;
10568 
10569 				// Load scripts that needs to be loaded
10570 				tinymce.each(loadingScripts, function(url) {
10571 					// Script is already loaded then execute script callbacks directly
10572 					if (states[url] == LOADED) {
10573 						execScriptLoadedCallbacks(url);
10574 						return;
10575 					}
10576 
10577 					// Is script not loading then start loading it
10578 					if (states[url] != LOADING) {
10579 						states[url] = LOADING;
10580 						loading++;
10581 
10582 						loadScript(url, function() {
10583 							states[url] = LOADED;
10584 							loading--;
10585 
10586 							execScriptLoadedCallbacks(url);
10587 
10588 							// Load more scripts if they where added by the recently loaded script
10589 							loadScripts();
10590 						});
10591 					}
10592 				});
10593 
10594 				// No scripts are currently loading then execute all pending queue loaded callbacks
10595 				if (!loading) {
10596 					tinymce.each(queueLoadedCallbacks, function(callback) {
10597 						callback.func.call(callback.scope);
10598 					});
10599 
10600 					queueLoadedCallbacks.length = 0;
10601 				}
10602 			};
10603 
10604 			loadScripts();
10605 		};
10606 	};
10607 
10608 	// Global script loader
10609 	tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
10610 })(tinymce);
10611 
10612 (function(tinymce) {
10613 	tinymce.dom.RangeUtils = function(dom) {
10614 		var INVISIBLE_CHAR = '\uFEFF';
10615 
10616 		this.walk = function(rng, callback) {
10617 			var startContainer = rng.startContainer,
10618 				startOffset = rng.startOffset,
10619 				endContainer = rng.endContainer,
10620 				endOffset = rng.endOffset,
10621 				ancestor, startPoint,
10622 				endPoint, node, parent, siblings, nodes;
10623 
10624 			// Handle table cell selection the table plugin enables
10625 			// you to fake select table cells and perform formatting actions on them
10626 			nodes = dom.select('td.mceSelected,th.mceSelected');
10627 			if (nodes.length > 0) {
10628 				tinymce.each(nodes, function(node) {
10629 					callback([node]);
10630 				});
10631 
10632 				return;
10633 			}
10634 
10635 			function exclude(nodes) {
10636 				var node;
10637 
10638 				// First node is excluded
10639 				node = nodes[0];
10640 				if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
10641 					nodes.splice(0, 1);
10642 				}
10643 
10644 				// Last node is excluded
10645 				node = nodes[nodes.length - 1];
10646 				if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
10647 					nodes.splice(nodes.length - 1, 1);
10648 				}
10649 
10650 				return nodes;
10651 			};
10652 
10653 			function collectSiblings(node, name, end_node) {
10654 				var siblings = [];
10655 
10656 				for (; node && node != end_node; node = node[name])
10657 					siblings.push(node);
10658 
10659 				return siblings;
10660 			};
10661 
10662 			function findEndPoint(node, root) {
10663 				do {
10664 					if (node.parentNode == root)
10665 						return node;
10666 
10667 					node = node.parentNode;
10668 				} while(node);
10669 			};
10670 
10671 			function walkBoundary(start_node, end_node, next) {
10672 				var siblingName = next ? 'nextSibling' : 'previousSibling';
10673 
10674 				for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
10675 					parent = node.parentNode;
10676 					siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
10677 
10678 					if (siblings.length) {
10679 						if (!next)
10680 							siblings.reverse();
10681 
10682 						callback(exclude(siblings));
10683 					}
10684 				}
10685 			};
10686 
10687 			// If index based start position then resolve it
10688 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
10689 				startContainer = startContainer.childNodes[startOffset];
10690 
10691 			// If index based end position then resolve it
10692 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
10693 				endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
10694 
10695 			// Same container
10696 			if (startContainer == endContainer)
10697 				return callback(exclude([startContainer]));
10698 
10699 			// Find common ancestor and end points
10700 			ancestor = dom.findCommonAncestor(startContainer, endContainer);
10701 				
10702 			// Process left side
10703 			for (node = startContainer; node; node = node.parentNode) {
10704 				if (node === endContainer)
10705 					return walkBoundary(startContainer, ancestor, true);
10706 
10707 				if (node === ancestor)
10708 					break;
10709 			}
10710 
10711 			// Process right side
10712 			for (node = endContainer; node; node = node.parentNode) {
10713 				if (node === startContainer)
10714 					return walkBoundary(endContainer, ancestor);
10715 
10716 				if (node === ancestor)
10717 					break;
10718 			}
10719 
10720 			// Find start/end point
10721 			startPoint = findEndPoint(startContainer, ancestor) || startContainer;
10722 			endPoint = findEndPoint(endContainer, ancestor) || endContainer;
10723 
10724 			// Walk left leaf
10725 			walkBoundary(startContainer, startPoint, true);
10726 
10727 			// Walk the middle from start to end point
10728 			siblings = collectSiblings(
10729 				startPoint == startContainer ? startPoint : startPoint.nextSibling,
10730 				'nextSibling',
10731 				endPoint == endContainer ? endPoint.nextSibling : endPoint
10732 			);
10733 
10734 			if (siblings.length)
10735 				callback(exclude(siblings));
10736 
10737 			// Walk right leaf
10738 			walkBoundary(endContainer, endPoint);
10739 		};
10740 
10741 		this.split = function(rng) {
10742 			var startContainer = rng.startContainer,
10743 				startOffset = rng.startOffset,
10744 				endContainer = rng.endContainer,
10745 				endOffset = rng.endOffset;
10746 
10747 			function splitText(node, offset) {
10748 				return node.splitText(offset);
10749 			};
10750 
10751 			// Handle single text node
10752 			if (startContainer == endContainer && startContainer.nodeType == 3) {
10753 				if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
10754 					endContainer = splitText(startContainer, startOffset);
10755 					startContainer = endContainer.previousSibling;
10756 
10757 					if (endOffset > startOffset) {
10758 						endOffset = endOffset - startOffset;
10759 						startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
10760 						endOffset = endContainer.nodeValue.length;
10761 						startOffset = 0;
10762 					} else {
10763 						endOffset = 0;
10764 					}
10765 				}
10766 			} else {
10767 				// Split startContainer text node if needed
10768 				if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
10769 					startContainer = splitText(startContainer, startOffset);
10770 					startOffset = 0;
10771 				}
10772 
10773 				// Split endContainer text node if needed
10774 				if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
10775 					endContainer = splitText(endContainer, endOffset).previousSibling;
10776 					endOffset = endContainer.nodeValue.length;
10777 				}
10778 			}
10779 
10780 			return {
10781 				startContainer : startContainer,
10782 				startOffset : startOffset,
10783 				endContainer : endContainer,
10784 				endOffset : endOffset
10785 			};
10786 		};
10787 
10788 	};
10789 
10790 	tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
10791 		if (rng1 && rng2) {
10792 			// Compare native IE ranges
10793 			if (rng1.item || rng1.duplicate) {
10794 				// Both are control ranges and the selected element matches
10795 				if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
10796 					return true;
10797 
10798 				// Both are text ranges and the range matches
10799 				if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
10800 					return true;
10801 			} else {
10802 				// Compare w3c ranges
10803 				return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
10804 			}
10805 		}
10806 
10807 		return false;
10808 	};
10809 })(tinymce);
10810 
10811 (function(tinymce) {
10812 	var Event = tinymce.dom.Event, each = tinymce.each;
10813 
10814 	tinymce.create('tinymce.ui.KeyboardNavigation', {
10815 		KeyboardNavigation: function(settings, dom) {
10816 			var t = this, root = settings.root, items = settings.items,
10817 					enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
10818 					excludeFromTabOrder = settings.excludeFromTabOrder,
10819 					itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
10820 
10821 			dom = dom || tinymce.DOM;
10822 
10823 			itemFocussed = function(evt) {
10824 				focussedId = evt.target.id;
10825 			};
10826 			
10827 			itemBlurred = function(evt) {
10828 				dom.setAttrib(evt.target.id, 'tabindex', '-1');
10829 			};
10830 			
10831 			rootFocussed = function(evt) {
10832 				var item = dom.get(focussedId);
10833 				dom.setAttrib(item, 'tabindex', '0');
10834 				item.focus();
10835 			};
10836 			
10837 			t.focus = function() {
10838 				dom.get(focussedId).focus();
10839 			};
10840 
10841 			t.destroy = function() {
10842 				each(items, function(item) {
10843 					var elm = dom.get(item.id);
10844 
10845 					dom.unbind(elm, 'focus', itemFocussed);
10846 					dom.unbind(elm, 'blur', itemBlurred);
10847 				});
10848 
10849 				var rootElm = dom.get(root);
10850 				dom.unbind(rootElm, 'focus', rootFocussed);
10851 				dom.unbind(rootElm, 'keydown', rootKeydown);
10852 
10853 				items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
10854 				t.destroy = function() {};
10855 			};
10856 			
10857 			t.moveFocus = function(dir, evt) {
10858 				var idx = -1, controls = t.controls, newFocus;
10859 
10860 				if (!focussedId)
10861 					return;
10862 
10863 				each(items, function(item, index) {
10864 					if (item.id === focussedId) {
10865 						idx = index;
10866 						return false;
10867 					}
10868 				});
10869 
10870 				idx += dir;
10871 				if (idx < 0) {
10872 					idx = items.length - 1;
10873 				} else if (idx >= items.length) {
10874 					idx = 0;
10875 				}
10876 				
10877 				newFocus = items[idx];
10878 				dom.setAttrib(focussedId, 'tabindex', '-1');
10879 				dom.setAttrib(newFocus.id, 'tabindex', '0');
10880 				dom.get(newFocus.id).focus();
10881 
10882 				if (settings.actOnFocus) {
10883 					settings.onAction(newFocus.id);
10884 				}
10885 
10886 				if (evt)
10887 					Event.cancel(evt);
10888 			};
10889 			
10890 			rootKeydown = function(evt) {
10891 				var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
10892 				
10893 				switch (evt.keyCode) {
10894 					case DOM_VK_LEFT:
10895 						if (enableLeftRight) t.moveFocus(-1);
10896 						break;
10897 	
10898 					case DOM_VK_RIGHT:
10899 						if (enableLeftRight) t.moveFocus(1);
10900 						break;
10901 	
10902 					case DOM_VK_UP:
10903 						if (enableUpDown) t.moveFocus(-1);
10904 						break;
10905 
10906 					case DOM_VK_DOWN:
10907 						if (enableUpDown) t.moveFocus(1);
10908 						break;
10909 
10910 					case DOM_VK_ESCAPE:
10911 						if (settings.onCancel) {
10912 							settings.onCancel();
10913 							Event.cancel(evt);
10914 						}
10915 						break;
10916 
10917 					case DOM_VK_ENTER:
10918 					case DOM_VK_RETURN:
10919 					case DOM_VK_SPACE:
10920 						if (settings.onAction) {
10921 							settings.onAction(focussedId);
10922 							Event.cancel(evt);
10923 						}
10924 						break;
10925 				}
10926 			};
10927 
10928 			// Set up state and listeners for each item.
10929 			each(items, function(item, idx) {
10930 				var tabindex, elm;
10931 
10932 				if (!item.id) {
10933 					item.id = dom.uniqueId('_mce_item_');
10934 				}
10935 
10936 				elm = dom.get(item.id);
10937 
10938 				if (excludeFromTabOrder) {
10939 					dom.bind(elm, 'blur', itemBlurred);
10940 					tabindex = '-1';
10941 				} else {
10942 					tabindex = (idx === 0 ? '0' : '-1');
10943 				}
10944 
10945 				elm.setAttribute('tabindex', tabindex);
10946 				dom.bind(elm, 'focus', itemFocussed);
10947 			});
10948 			
10949 			// Setup initial state for root element.
10950 			if (items[0]){
10951 				focussedId = items[0].id;
10952 			}
10953 
10954 			dom.setAttrib(root, 'tabindex', '-1');
10955 
10956 			// Setup listeners for root element.
10957 			var rootElm = dom.get(root);
10958 			dom.bind(rootElm, 'focus', rootFocussed);
10959 			dom.bind(rootElm, 'keydown', rootKeydown);
10960 		}
10961 	});
10962 })(tinymce);
10963 
10964 (function(tinymce) {
10965 	// Shorten class names
10966 	var DOM = tinymce.DOM, is = tinymce.is;
10967 
10968 	tinymce.create('tinymce.ui.Control', {
10969 		Control : function(id, s, editor) {
10970 			this.id = id;
10971 			this.settings = s = s || {};
10972 			this.rendered = false;
10973 			this.onRender = new tinymce.util.Dispatcher(this);
10974 			this.classPrefix = '';
10975 			this.scope = s.scope || this;
10976 			this.disabled = 0;
10977 			this.active = 0;
10978 			this.editor = editor;
10979 		},
10980 		
10981 		setAriaProperty : function(property, value) {
10982 			var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
10983 			if (element) {
10984 				DOM.setAttrib(element, 'aria-' + property, !!value);
10985 			}
10986 		},
10987 		
10988 		focus : function() {
10989 			DOM.get(this.id).focus();
10990 		},
10991 
10992 		setDisabled : function(s) {
10993 			if (s != this.disabled) {
10994 				this.setAriaProperty('disabled', s);
10995 
10996 				this.setState('Disabled', s);
10997 				this.setState('Enabled', !s);
10998 				this.disabled = s;
10999 			}
11000 		},
11001 
11002 		isDisabled : function() {
11003 			return this.disabled;
11004 		},
11005 
11006 		setActive : function(s) {
11007 			if (s != this.active) {
11008 				this.setState('Active', s);
11009 				this.active = s;
11010 				this.setAriaProperty('pressed', s);
11011 			}
11012 		},
11013 
11014 		isActive : function() {
11015 			return this.active;
11016 		},
11017 
11018 		setState : function(c, s) {
11019 			var n = DOM.get(this.id);
11020 
11021 			c = this.classPrefix + c;
11022 
11023 			if (s)
11024 				DOM.addClass(n, c);
11025 			else
11026 				DOM.removeClass(n, c);
11027 		},
11028 
11029 		isRendered : function() {
11030 			return this.rendered;
11031 		},
11032 
11033 		renderHTML : function() {
11034 		},
11035 
11036 		renderTo : function(n) {
11037 			DOM.setHTML(n, this.renderHTML());
11038 		},
11039 
11040 		postRender : function() {
11041 			var t = this, b;
11042 
11043 			// Set pending states
11044 			if (is(t.disabled)) {
11045 				b = t.disabled;
11046 				t.disabled = -1;
11047 				t.setDisabled(b);
11048 			}
11049 
11050 			if (is(t.active)) {
11051 				b = t.active;
11052 				t.active = -1;
11053 				t.setActive(b);
11054 			}
11055 		},
11056 
11057 		remove : function() {
11058 			DOM.remove(this.id);
11059 			this.destroy();
11060 		},
11061 
11062 		destroy : function() {
11063 			tinymce.dom.Event.clear(this.id);
11064 		}
11065 	});
11066 })(tinymce);
11067 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
11068 	Container : function(id, s, editor) {
11069 		this.parent(id, s, editor);
11070 
11071 		this.controls = [];
11072 
11073 		this.lookup = {};
11074 	},
11075 
11076 	add : function(c) {
11077 		this.lookup[c.id] = c;
11078 		this.controls.push(c);
11079 
11080 		return c;
11081 	},
11082 
11083 	get : function(n) {
11084 		return this.lookup[n];
11085 	}
11086 });
11087 
11088 
11089 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
11090 	Separator : function(id, s) {
11091 		this.parent(id, s);
11092 		this.classPrefix = 'mceSeparator';
11093 		this.setDisabled(true);
11094 	},
11095 
11096 	renderHTML : function() {
11097 		return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
11098 	}
11099 });
11100 
11101 (function(tinymce) {
11102 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11103 
11104 	tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
11105 		MenuItem : function(id, s) {
11106 			this.parent(id, s);
11107 			this.classPrefix = 'mceMenuItem';
11108 		},
11109 
11110 		setSelected : function(s) {
11111 			this.setState('Selected', s);
11112 			this.setAriaProperty('checked', !!s);
11113 			this.selected = s;
11114 		},
11115 
11116 		isSelected : function() {
11117 			return this.selected;
11118 		},
11119 
11120 		postRender : function() {
11121 			var t = this;
11122 			
11123 			t.parent();
11124 
11125 			// Set pending state
11126 			if (is(t.selected))
11127 				t.setSelected(t.selected);
11128 		}
11129 	});
11130 })(tinymce);
11131 
11132 (function(tinymce) {
11133 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11134 
11135 	tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
11136 		Menu : function(id, s) {
11137 			var t = this;
11138 
11139 			t.parent(id, s);
11140 			t.items = {};
11141 			t.collapsed = false;
11142 			t.menuCount = 0;
11143 			t.onAddItem = new tinymce.util.Dispatcher(this);
11144 		},
11145 
11146 		expand : function(d) {
11147 			var t = this;
11148 
11149 			if (d) {
11150 				walk(t, function(o) {
11151 					if (o.expand)
11152 						o.expand();
11153 				}, 'items', t);
11154 			}
11155 
11156 			t.collapsed = false;
11157 		},
11158 
11159 		collapse : function(d) {
11160 			var t = this;
11161 
11162 			if (d) {
11163 				walk(t, function(o) {
11164 					if (o.collapse)
11165 						o.collapse();
11166 				}, 'items', t);
11167 			}
11168 
11169 			t.collapsed = true;
11170 		},
11171 
11172 		isCollapsed : function() {
11173 			return this.collapsed;
11174 		},
11175 
11176 		add : function(o) {
11177 			if (!o.settings)
11178 				o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
11179 
11180 			this.onAddItem.dispatch(this, o);
11181 
11182 			return this.items[o.id] = o;
11183 		},
11184 
11185 		addSeparator : function() {
11186 			return this.add({separator : true});
11187 		},
11188 
11189 		addMenu : function(o) {
11190 			if (!o.collapse)
11191 				o = this.createMenu(o);
11192 
11193 			this.menuCount++;
11194 
11195 			return this.add(o);
11196 		},
11197 
11198 		hasMenus : function() {
11199 			return this.menuCount !== 0;
11200 		},
11201 
11202 		remove : function(o) {
11203 			delete this.items[o.id];
11204 		},
11205 
11206 		removeAll : function() {
11207 			var t = this;
11208 
11209 			walk(t, function(o) {
11210 				if (o.removeAll)
11211 					o.removeAll();
11212 				else
11213 					o.remove();
11214 
11215 				o.destroy();
11216 			}, 'items', t);
11217 
11218 			t.items = {};
11219 		},
11220 
11221 		createMenu : function(o) {
11222 			var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
11223 
11224 			m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
11225 
11226 			return m;
11227 		}
11228 	});
11229 })(tinymce);
11230 (function(tinymce) {
11231 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
11232 
11233 	tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
11234 		DropMenu : function(id, s) {
11235 			s = s || {};
11236 			s.container = s.container || DOM.doc.body;
11237 			s.offset_x = s.offset_x || 0;
11238 			s.offset_y = s.offset_y || 0;
11239 			s.vp_offset_x = s.vp_offset_x || 0;
11240 			s.vp_offset_y = s.vp_offset_y || 0;
11241 
11242 			if (is(s.icons) && !s.icons)
11243 				s['class'] += ' mceNoIcons';
11244 
11245 			this.parent(id, s);
11246 			this.onShowMenu = new tinymce.util.Dispatcher(this);
11247 			this.onHideMenu = new tinymce.util.Dispatcher(this);
11248 			this.classPrefix = 'mceMenu';
11249 		},
11250 
11251 		createMenu : function(s) {
11252 			var t = this, cs = t.settings, m;
11253 
11254 			s.container = s.container || cs.container;
11255 			s.parent = t;
11256 			s.constrain = s.constrain || cs.constrain;
11257 			s['class'] = s['class'] || cs['class'];
11258 			s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
11259 			s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
11260 			s.keyboard_focus = cs.keyboard_focus;
11261 			m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
11262 
11263 			m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
11264 
11265 			return m;
11266 		},
11267 		
11268 		focus : function() {
11269 			var t = this;
11270 			if (t.keyboardNav) {
11271 				t.keyboardNav.focus();
11272 			}
11273 		},
11274 
11275 		update : function() {
11276 			var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
11277 
11278 			tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth;
11279 			th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight;
11280 
11281 			if (!DOM.boxModel)
11282 				t.element.setStyles({width : tw + 2, height : th + 2});
11283 			else
11284 				t.element.setStyles({width : tw, height : th});
11285 
11286 			if (s.max_width)
11287 				DOM.setStyle(co, 'width', tw);
11288 
11289 			if (s.max_height) {
11290 				DOM.setStyle(co, 'height', th);
11291 
11292 				if (tb.clientHeight < s.max_height)
11293 					DOM.setStyle(co, 'overflow', 'hidden');
11294 			}
11295 		},
11296 
11297 		showMenu : function(x, y, px) {
11298 			var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
11299 
11300 			t.collapse(1);
11301 
11302 			if (t.isMenuVisible)
11303 				return;
11304 
11305 			if (!t.rendered) {
11306 				co = DOM.add(t.settings.container, t.renderNode());
11307 
11308 				each(t.items, function(o) {
11309 					o.postRender();
11310 				});
11311 
11312 				t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11313 			} else
11314 				co = DOM.get('menu_' + t.id);
11315 
11316 			// Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
11317 			if (!tinymce.isOpera)
11318 				DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
11319 
11320 			DOM.show(co);
11321 			t.update();
11322 
11323 			x += s.offset_x || 0;
11324 			y += s.offset_y || 0;
11325 			vp.w -= 4;
11326 			vp.h -= 4;
11327 
11328 			// Move inside viewport if not submenu
11329 			if (s.constrain) {
11330 				w = co.clientWidth - ot;
11331 				h = co.clientHeight - ot;
11332 				mx = vp.x + vp.w;
11333 				my = vp.y + vp.h;
11334 
11335 				if ((x + s.vp_offset_x + w) > mx)
11336 					x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
11337 
11338 				if ((y + s.vp_offset_y + h) > my)
11339 					y = Math.max(0, (my - s.vp_offset_y) - h);
11340 			}
11341 
11342 			DOM.setStyles(co, {left : x , top : y});
11343 			t.element.update();
11344 
11345 			t.isMenuVisible = 1;
11346 			t.mouseClickFunc = Event.add(co, 'click', function(e) {
11347 				var m;
11348 
11349 				e = e.target;
11350 
11351 				if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
11352 					m = t.items[e.id];
11353 
11354 					if (m.isDisabled())
11355 						return;
11356 
11357 					dm = t;
11358 
11359 					while (dm) {
11360 						if (dm.hideMenu)
11361 							dm.hideMenu();
11362 
11363 						dm = dm.settings.parent;
11364 					}
11365 
11366 					if (m.settings.onclick)
11367 						m.settings.onclick(e);
11368 
11369 					return false; // Cancel to fix onbeforeunload problem
11370 				}
11371 			});
11372 
11373 			if (t.hasMenus()) {
11374 				t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
11375 					var m, r, mi;
11376 
11377 					e = e.target;
11378 					if (e && (e = DOM.getParent(e, 'tr'))) {
11379 						m = t.items[e.id];
11380 
11381 						if (t.lastMenu)
11382 							t.lastMenu.collapse(1);
11383 
11384 						if (m.isDisabled())
11385 							return;
11386 
11387 						if (e && DOM.hasClass(e, cp + 'ItemSub')) {
11388 							//p = DOM.getPos(s.container);
11389 							r = DOM.getRect(e);
11390 							m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
11391 							t.lastMenu = m;
11392 							DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
11393 						}
11394 					}
11395 				});
11396 			}
11397 			
11398 			Event.add(co, 'keydown', t._keyHandler, t);
11399 
11400 			t.onShowMenu.dispatch(t);
11401 
11402 			if (s.keyboard_focus) { 
11403 				t._setupKeyboardNav(); 
11404 			}
11405 		},
11406 
11407 		hideMenu : function(c) {
11408 			var t = this, co = DOM.get('menu_' + t.id), e;
11409 
11410 			if (!t.isMenuVisible)
11411 				return;
11412 
11413 			if (t.keyboardNav) t.keyboardNav.destroy();
11414 			Event.remove(co, 'mouseover', t.mouseOverFunc);
11415 			Event.remove(co, 'click', t.mouseClickFunc);
11416 			Event.remove(co, 'keydown', t._keyHandler);
11417 			DOM.hide(co);
11418 			t.isMenuVisible = 0;
11419 
11420 			if (!c)
11421 				t.collapse(1);
11422 
11423 			if (t.element)
11424 				t.element.hide();
11425 
11426 			if (e = DOM.get(t.id))
11427 				DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
11428 
11429 			t.onHideMenu.dispatch(t);
11430 		},
11431 
11432 		add : function(o) {
11433 			var t = this, co;
11434 
11435 			o = t.parent(o);
11436 
11437 			if (t.isRendered && (co = DOM.get('menu_' + t.id)))
11438 				t._add(DOM.select('tbody', co)[0], o);
11439 
11440 			return o;
11441 		},
11442 
11443 		collapse : function(d) {
11444 			this.parent(d);
11445 			this.hideMenu(1);
11446 		},
11447 
11448 		remove : function(o) {
11449 			DOM.remove(o.id);
11450 			this.destroy();
11451 
11452 			return this.parent(o);
11453 		},
11454 
11455 		destroy : function() {
11456 			var t = this, co = DOM.get('menu_' + t.id);
11457 
11458 			if (t.keyboardNav) t.keyboardNav.destroy();
11459 			Event.remove(co, 'mouseover', t.mouseOverFunc);
11460 			Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
11461 			Event.remove(co, 'click', t.mouseClickFunc);
11462 			Event.remove(co, 'keydown', t._keyHandler);
11463 
11464 			if (t.element)
11465 				t.element.remove();
11466 
11467 			DOM.remove(co);
11468 		},
11469 
11470 		renderNode : function() {
11471 			var t = this, s = t.settings, n, tb, co, w;
11472 
11473 			w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
11474 			if (t.settings.parent) {
11475 				DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
11476 			}
11477 			co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
11478 			t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11479 
11480 			if (s.menu_line)
11481 				DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
11482 
11483 //			n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
11484 			n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
11485 			tb = DOM.add(n, 'tbody');
11486 
11487 			each(t.items, function(o) {
11488 				t._add(tb, o);
11489 			});
11490 
11491 			t.rendered = true;
11492 
11493 			return w;
11494 		},
11495 
11496 		// Internal functions
11497 		_setupKeyboardNav : function(){
11498 			var contextMenu, menuItems, t=this; 
11499 			contextMenu = DOM.get('menu_' + t.id);
11500 			menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
11501 			menuItems.splice(0,0,contextMenu);
11502 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
11503 				root: 'menu_' + t.id,
11504 				items: menuItems,
11505 				onCancel: function() {
11506 					t.hideMenu();
11507 				},
11508 				enableUpDown: true
11509 			});
11510 			contextMenu.focus();
11511 		},
11512 
11513 		_keyHandler : function(evt) {
11514 			var t = this, e;
11515 			switch (evt.keyCode) {
11516 				case 37: // Left
11517 					if (t.settings.parent) {
11518 						t.hideMenu();
11519 						t.settings.parent.focus();
11520 						Event.cancel(evt);
11521 					}
11522 					break;
11523 				case 39: // Right
11524 					if (t.mouseOverFunc)
11525 						t.mouseOverFunc(evt);
11526 					break;
11527 			}
11528 		},
11529 
11530 		_add : function(tb, o) {
11531 			var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
11532 
11533 			if (s.separator) {
11534 				ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
11535 				DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
11536 
11537 				if (n = ro.previousSibling)
11538 					DOM.addClass(n, 'mceLast');
11539 
11540 				return;
11541 			}
11542 
11543 			n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
11544 			n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
11545 			n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
11546 
11547 			if (s.parent) {
11548 				DOM.setAttrib(a, 'aria-haspopup', 'true');
11549 				DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
11550 			}
11551 
11552 			DOM.addClass(it, s['class']);
11553 //			n = DOM.add(n, 'span', {'class' : 'item'});
11554 
11555 			ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
11556 
11557 			if (s.icon_src)
11558 				DOM.add(ic, 'img', {src : s.icon_src});
11559 
11560 			n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
11561 
11562 			if (o.settings.style) {
11563 				if (typeof o.settings.style == "function")
11564 					o.settings.style = o.settings.style();
11565 
11566 				DOM.setAttrib(n, 'style', o.settings.style);
11567 			}
11568 
11569 			if (tb.childNodes.length == 1)
11570 				DOM.addClass(ro, 'mceFirst');
11571 
11572 			if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
11573 				DOM.addClass(ro, 'mceFirst');
11574 
11575 			if (o.collapse)
11576 				DOM.addClass(ro, cp + 'ItemSub');
11577 
11578 			if (n = ro.previousSibling)
11579 				DOM.removeClass(n, 'mceLast');
11580 
11581 			DOM.addClass(ro, 'mceLast');
11582 		}
11583 	});
11584 })(tinymce);
11585 (function(tinymce) {
11586 	var DOM = tinymce.DOM;
11587 
11588 	tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
11589 		Button : function(id, s, ed) {
11590 			this.parent(id, s, ed);
11591 			this.classPrefix = 'mceButton';
11592 		},
11593 
11594 		renderHTML : function() {
11595 			var cp = this.classPrefix, s = this.settings, h, l;
11596 
11597 			l = DOM.encode(s.label || '');
11598 			h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
11599 			if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )
11600 				h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11601 			else
11602 				h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11603 
11604 			h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 
11605 			h += '</a>';
11606 			return h;
11607 		},
11608 
11609 		postRender : function() {
11610 			var t = this, s = t.settings, imgBookmark;
11611 
11612 			// In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so
11613 			// need to keep the selection in case the selection is lost
11614 			if (tinymce.isIE && t.editor) {
11615 				tinymce.dom.Event.add(t.id, 'mousedown', function(e) {
11616 					var nodeName = t.editor.selection.getNode().nodeName;
11617 					imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null;
11618 				});
11619 			}
11620 			tinymce.dom.Event.add(t.id, 'click', function(e) {
11621 				if (!t.isDisabled()) {
11622 					// restore the selection in case the selection is lost in IE
11623 					if (tinymce.isIE && t.editor && imgBookmark !== null) {
11624 						t.editor.selection.moveToBookmark(imgBookmark);
11625 					}
11626 					return s.onclick.call(s.scope, e);
11627 				}
11628 			});
11629 			tinymce.dom.Event.add(t.id, 'keyup', function(e) {
11630 				if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)
11631 					return s.onclick.call(s.scope, e);
11632 			});
11633 		}
11634 	});
11635 })(tinymce);
11636 
11637 (function(tinymce) {
11638 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
11639 
11640 	tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
11641 		ListBox : function(id, s, ed) {
11642 			var t = this;
11643 
11644 			t.parent(id, s, ed);
11645 
11646 			t.items = [];
11647 
11648 			t.onChange = new Dispatcher(t);
11649 
11650 			t.onPostRender = new Dispatcher(t);
11651 
11652 			t.onAdd = new Dispatcher(t);
11653 
11654 			t.onRenderMenu = new tinymce.util.Dispatcher(this);
11655 
11656 			t.classPrefix = 'mceListBox';
11657 			t.marked = {};
11658 		},
11659 
11660 		select : function(va) {
11661 			var t = this, fv, f;
11662 
11663 			t.marked = {};
11664 
11665 			if (va == undef)
11666 				return t.selectByIndex(-1);
11667 
11668 			// Is string or number make function selector
11669 			if (va && typeof(va)=="function")
11670 				f = va;
11671 			else {
11672 				f = function(v) {
11673 					return v == va;
11674 				};
11675 			}
11676 
11677 			// Do we need to do something?
11678 			if (va != t.selectedValue) {
11679 				// Find item
11680 				each(t.items, function(o, i) {
11681 					if (f(o.value)) {
11682 						fv = 1;
11683 						t.selectByIndex(i);
11684 						return false;
11685 					}
11686 				});
11687 
11688 				if (!fv)
11689 					t.selectByIndex(-1);
11690 			}
11691 		},
11692 
11693 		selectByIndex : function(idx) {
11694 			var t = this, e, o, label;
11695 
11696 			t.marked = {};
11697 
11698 			if (idx != t.selectedIndex) {
11699 				e = DOM.get(t.id + '_text');
11700 				label = DOM.get(t.id + '_voiceDesc');
11701 				o = t.items[idx];
11702 
11703 				if (o) {
11704 					t.selectedValue = o.value;
11705 					t.selectedIndex = idx;
11706 					DOM.setHTML(e, DOM.encode(o.title));
11707 					DOM.setHTML(label, t.settings.title + " - " + o.title);
11708 					DOM.removeClass(e, 'mceTitle');
11709 					DOM.setAttrib(t.id, 'aria-valuenow', o.title);
11710 				} else {
11711 					DOM.setHTML(e, DOM.encode(t.settings.title));
11712 					DOM.setHTML(label, DOM.encode(t.settings.title));
11713 					DOM.addClass(e, 'mceTitle');
11714 					t.selectedValue = t.selectedIndex = null;
11715 					DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
11716 				}
11717 				e = 0;
11718 			}
11719 		},
11720 
11721 		mark : function(value) {
11722 			this.marked[value] = true;
11723 		},
11724 
11725 		add : function(n, v, o) {
11726 			var t = this;
11727 
11728 			o = o || {};
11729 			o = tinymce.extend(o, {
11730 				title : n,
11731 				value : v
11732 			});
11733 
11734 			t.items.push(o);
11735 			t.onAdd.dispatch(t, o);
11736 		},
11737 
11738 		getLength : function() {
11739 			return this.items.length;
11740 		},
11741 
11742 		renderHTML : function() {
11743 			var h = '', t = this, s = t.settings, cp = t.classPrefix;
11744 
11745 			h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
11746 			h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
11747 			h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
11748 			h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
11749 			h += '</tr></tbody></table></span>';
11750 
11751 			return h;
11752 		},
11753 
11754 		showMenu : function() {
11755 			var t = this, p2, e = DOM.get(this.id), m;
11756 
11757 			if (t.isDisabled() || t.items.length === 0)
11758 				return;
11759 
11760 			if (t.menu && t.menu.isMenuVisible)
11761 				return t.hideMenu();
11762 
11763 			if (!t.isMenuRendered) {
11764 				t.renderMenu();
11765 				t.isMenuRendered = true;
11766 			}
11767 
11768 			p2 = DOM.getPos(e);
11769 
11770 			m = t.menu;
11771 			m.settings.offset_x = p2.x;
11772 			m.settings.offset_y = p2.y;
11773 			m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
11774 
11775 			// Select in menu
11776 			each(t.items, function(o) {
11777 				if (m.items[o.id]) {
11778 					m.items[o.id].setSelected(0);
11779 				}
11780 			});
11781 
11782 			each(t.items, function(o) {
11783 				if (m.items[o.id] && t.marked[o.value]) {
11784 					m.items[o.id].setSelected(1);
11785 				}
11786 
11787 				if (o.value === t.selectedValue) {
11788 					m.items[o.id].setSelected(1);
11789 				}
11790 			});
11791 
11792 			m.showMenu(0, e.clientHeight);
11793 
11794 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
11795 			DOM.addClass(t.id, t.classPrefix + 'Selected');
11796 
11797 			//DOM.get(t.id + '_text').focus();
11798 		},
11799 
11800 		hideMenu : function(e) {
11801 			var t = this;
11802 
11803 			if (t.menu && t.menu.isMenuVisible) {
11804 				DOM.removeClass(t.id, t.classPrefix + 'Selected');
11805 
11806 				// Prevent double toogles by canceling the mouse click event to the button
11807 				if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
11808 					return;
11809 
11810 				if (!e || !DOM.getParent(e.target, '.mceMenu')) {
11811 					DOM.removeClass(t.id, t.classPrefix + 'Selected');
11812 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
11813 					t.menu.hideMenu();
11814 				}
11815 			}
11816 		},
11817 
11818 		renderMenu : function() {
11819 			var t = this, m;
11820 
11821 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
11822 				menu_line : 1,
11823 				'class' : t.classPrefix + 'Menu mceNoIcons',
11824 				max_width : 250,
11825 				max_height : 150
11826 			});
11827 
11828 			m.onHideMenu.add(function() {
11829 				t.hideMenu();
11830 				t.focus();
11831 			});
11832 
11833 			m.add({
11834 				title : t.settings.title,
11835 				'class' : 'mceMenuItemTitle',
11836 				onclick : function() {
11837 					if (t.settings.onselect('') !== false)
11838 						t.select(''); // Must be runned after
11839 				}
11840 			});
11841 
11842 			each(t.items, function(o) {
11843 				// No value then treat it as a title
11844 				if (o.value === undef) {
11845 					m.add({
11846 						title : o.title,
11847 						role : "option",
11848 						'class' : 'mceMenuItemTitle',
11849 						onclick : function() {
11850 							if (t.settings.onselect('') !== false)
11851 								t.select(''); // Must be runned after
11852 						}
11853 					});
11854 				} else {
11855 					o.id = DOM.uniqueId();
11856 					o.role= "option";
11857 					o.onclick = function() {
11858 						if (t.settings.onselect(o.value) !== false)
11859 							t.select(o.value); // Must be runned after
11860 					};
11861 
11862 					m.add(o);
11863 				}
11864 			});
11865 
11866 			t.onRenderMenu.dispatch(t, m);
11867 			t.menu = m;
11868 		},
11869 
11870 		postRender : function() {
11871 			var t = this, cp = t.classPrefix;
11872 
11873 			Event.add(t.id, 'click', t.showMenu, t);
11874 			Event.add(t.id, 'keydown', function(evt) {
11875 				if (evt.keyCode == 32) { // Space
11876 					t.showMenu(evt);
11877 					Event.cancel(evt);
11878 				}
11879 			});
11880 			Event.add(t.id, 'focus', function() {
11881 				if (!t._focused) {
11882 					t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
11883 						if (e.keyCode == 40) {
11884 							t.showMenu();
11885 							Event.cancel(e);
11886 						}
11887 					});
11888 					t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
11889 						var v;
11890 						if (e.keyCode == 13) {
11891 							// Fake select on enter
11892 							v = t.selectedValue;
11893 							t.selectedValue = null; // Needs to be null to fake change
11894 							Event.cancel(e);
11895 							t.settings.onselect(v);
11896 						}
11897 					});
11898 				}
11899 
11900 				t._focused = 1;
11901 			});
11902 			Event.add(t.id, 'blur', function() {
11903 				Event.remove(t.id, 'keydown', t.keyDownHandler);
11904 				Event.remove(t.id, 'keypress', t.keyPressHandler);
11905 				t._focused = 0;
11906 			});
11907 
11908 			// Old IE doesn't have hover on all elements
11909 			if (tinymce.isIE6 || !DOM.boxModel) {
11910 				Event.add(t.id, 'mouseover', function() {
11911 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
11912 						DOM.addClass(t.id, cp + 'Hover');
11913 				});
11914 
11915 				Event.add(t.id, 'mouseout', function() {
11916 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
11917 						DOM.removeClass(t.id, cp + 'Hover');
11918 				});
11919 			}
11920 
11921 			t.onPostRender.dispatch(t, DOM.get(t.id));
11922 		},
11923 
11924 		destroy : function() {
11925 			this.parent();
11926 
11927 			Event.clear(this.id + '_text');
11928 			Event.clear(this.id + '_open');
11929 		}
11930 	});
11931 })(tinymce);
11932 
11933 (function(tinymce) {
11934 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
11935 
11936 	tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
11937 		NativeListBox : function(id, s) {
11938 			this.parent(id, s);
11939 			this.classPrefix = 'mceNativeListBox';
11940 		},
11941 
11942 		setDisabled : function(s) {
11943 			DOM.get(this.id).disabled = s;
11944 			this.setAriaProperty('disabled', s);
11945 		},
11946 
11947 		isDisabled : function() {
11948 			return DOM.get(this.id).disabled;
11949 		},
11950 
11951 		select : function(va) {
11952 			var t = this, fv, f;
11953 
11954 			if (va == undef)
11955 				return t.selectByIndex(-1);
11956 
11957 			// Is string or number make function selector
11958 			if (va && typeof(va)=="function")
11959 				f = va;
11960 			else {
11961 				f = function(v) {
11962 					return v == va;
11963 				};
11964 			}
11965 
11966 			// Do we need to do something?
11967 			if (va != t.selectedValue) {
11968 				// Find item
11969 				each(t.items, function(o, i) {
11970 					if (f(o.value)) {
11971 						fv = 1;
11972 						t.selectByIndex(i);
11973 						return false;
11974 					}
11975 				});
11976 
11977 				if (!fv)
11978 					t.selectByIndex(-1);
11979 			}
11980 		},
11981 
11982 		selectByIndex : function(idx) {
11983 			DOM.get(this.id).selectedIndex = idx + 1;
11984 			this.selectedValue = this.items[idx] ? this.items[idx].value : null;
11985 		},
11986 
11987 		add : function(n, v, a) {
11988 			var o, t = this;
11989 
11990 			a = a || {};
11991 			a.value = v;
11992 
11993 			if (t.isRendered())
11994 				DOM.add(DOM.get(this.id), 'option', a, n);
11995 
11996 			o = {
11997 				title : n,
11998 				value : v,
11999 				attribs : a
12000 			};
12001 
12002 			t.items.push(o);
12003 			t.onAdd.dispatch(t, o);
12004 		},
12005 
12006 		getLength : function() {
12007 			return this.items.length;
12008 		},
12009 
12010 		renderHTML : function() {
12011 			var h, t = this;
12012 
12013 			h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
12014 
12015 			each(t.items, function(it) {
12016 				h += DOM.createHTML('option', {value : it.value}, it.title);
12017 			});
12018 
12019 			h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
12020 			h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
12021 			return h;
12022 		},
12023 
12024 		postRender : function() {
12025 			var t = this, ch, changeListenerAdded = true;
12026 
12027 			t.rendered = true;
12028 
12029 			function onChange(e) {
12030 				var v = t.items[e.target.selectedIndex - 1];
12031 
12032 				if (v && (v = v.value)) {
12033 					t.onChange.dispatch(t, v);
12034 
12035 					if (t.settings.onselect)
12036 						t.settings.onselect(v);
12037 				}
12038 			};
12039 
12040 			Event.add(t.id, 'change', onChange);
12041 
12042 			// Accessibility keyhandler
12043 			Event.add(t.id, 'keydown', function(e) {
12044 				var bf;
12045 
12046 				Event.remove(t.id, 'change', ch);
12047 				changeListenerAdded = false;
12048 
12049 				bf = Event.add(t.id, 'blur', function() {
12050 					if (changeListenerAdded) return;
12051 					changeListenerAdded = true;
12052 					Event.add(t.id, 'change', onChange);
12053 					Event.remove(t.id, 'blur', bf);
12054 				});
12055 
12056 				//prevent default left and right keys on chrome - so that the keyboard navigation is used.
12057 				if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {
12058 					return Event.prevent(e);
12059 				}
12060 				
12061 				if (e.keyCode == 13 || e.keyCode == 32) {
12062 					onChange(e);
12063 					return Event.cancel(e);
12064 				}
12065 			});
12066 
12067 			t.onPostRender.dispatch(t, DOM.get(t.id));
12068 		}
12069 	});
12070 })(tinymce);
12071 
12072 (function(tinymce) {
12073 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12074 
12075 	tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
12076 		MenuButton : function(id, s, ed) {
12077 			this.parent(id, s, ed);
12078 
12079 			this.onRenderMenu = new tinymce.util.Dispatcher(this);
12080 
12081 			s.menu_container = s.menu_container || DOM.doc.body;
12082 		},
12083 
12084 		showMenu : function() {
12085 			var t = this, p1, p2, e = DOM.get(t.id), m;
12086 
12087 			if (t.isDisabled())
12088 				return;
12089 
12090 			if (!t.isMenuRendered) {
12091 				t.renderMenu();
12092 				t.isMenuRendered = true;
12093 			}
12094 
12095 			if (t.isMenuVisible)
12096 				return t.hideMenu();
12097 
12098 			p1 = DOM.getPos(t.settings.menu_container);
12099 			p2 = DOM.getPos(e);
12100 
12101 			m = t.menu;
12102 			m.settings.offset_x = p2.x;
12103 			m.settings.offset_y = p2.y;
12104 			m.settings.vp_offset_x = p2.x;
12105 			m.settings.vp_offset_y = p2.y;
12106 			m.settings.keyboard_focus = t._focused;
12107 			m.showMenu(0, e.firstChild.clientHeight);
12108 
12109 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12110 			t.setState('Selected', 1);
12111 
12112 			t.isMenuVisible = 1;
12113 		},
12114 
12115 		renderMenu : function() {
12116 			var t = this, m;
12117 
12118 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
12119 				menu_line : 1,
12120 				'class' : this.classPrefix + 'Menu',
12121 				icons : t.settings.icons
12122 			});
12123 
12124 			m.onHideMenu.add(function() {
12125 				t.hideMenu();
12126 				t.focus();
12127 			});
12128 
12129 			t.onRenderMenu.dispatch(t, m);
12130 			t.menu = m;
12131 		},
12132 
12133 		hideMenu : function(e) {
12134 			var t = this;
12135 
12136 			// Prevent double toogles by canceling the mouse click event to the button
12137 			if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
12138 				return;
12139 
12140 			if (!e || !DOM.getParent(e.target, '.mceMenu')) {
12141 				t.setState('Selected', 0);
12142 				Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12143 				if (t.menu)
12144 					t.menu.hideMenu();
12145 			}
12146 
12147 			t.isMenuVisible = 0;
12148 		},
12149 
12150 		postRender : function() {
12151 			var t = this, s = t.settings;
12152 
12153 			Event.add(t.id, 'click', function() {
12154 				if (!t.isDisabled()) {
12155 					if (s.onclick)
12156 						s.onclick(t.value);
12157 
12158 					t.showMenu();
12159 				}
12160 			});
12161 		}
12162 	});
12163 })(tinymce);
12164 
12165 (function(tinymce) {
12166 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12167 
12168 	tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
12169 		SplitButton : function(id, s, ed) {
12170 			this.parent(id, s, ed);
12171 			this.classPrefix = 'mceSplitButton';
12172 		},
12173 
12174 		renderHTML : function() {
12175 			var h, t = this, s = t.settings, h1;
12176 
12177 			h = '<tbody><tr>';
12178 
12179 			if (s.image)
12180 				h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
12181 			else
12182 				h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
12183 
12184 			h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
12185 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
12186 	
12187 			h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
12188 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
12189 
12190 			h += '</tr></tbody>';
12191 			h = DOM.createHTML('table', { role: 'presentation',   'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
12192 			return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
12193 		},
12194 
12195 		postRender : function() {
12196 			var t = this, s = t.settings, activate;
12197 
12198 			if (s.onclick) {
12199 				activate = function(evt) {
12200 					if (!t.isDisabled()) {
12201 						s.onclick(t.value);
12202 						Event.cancel(evt);
12203 					}
12204 				};
12205 				Event.add(t.id + '_action', 'click', activate);
12206 				Event.add(t.id, ['click', 'keydown'], function(evt) {
12207 					var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
12208 					if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
12209 						activate();
12210 						Event.cancel(evt);
12211 					} else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
12212 						t.showMenu();
12213 						Event.cancel(evt);
12214 					}
12215 				});
12216 			}
12217 
12218 			Event.add(t.id + '_open', 'click', function (evt) {
12219 				t.showMenu();
12220 				Event.cancel(evt);
12221 			});
12222 			Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
12223 			Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
12224 
12225 			// Old IE doesn't have hover on all elements
12226 			if (tinymce.isIE6 || !DOM.boxModel) {
12227 				Event.add(t.id, 'mouseover', function() {
12228 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12229 						DOM.addClass(t.id, 'mceSplitButtonHover');
12230 				});
12231 
12232 				Event.add(t.id, 'mouseout', function() {
12233 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12234 						DOM.removeClass(t.id, 'mceSplitButtonHover');
12235 				});
12236 			}
12237 		},
12238 
12239 		destroy : function() {
12240 			this.parent();
12241 
12242 			Event.clear(this.id + '_action');
12243 			Event.clear(this.id + '_open');
12244 			Event.clear(this.id);
12245 		}
12246 	});
12247 })(tinymce);
12248 
12249 (function(tinymce) {
12250 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
12251 
12252 	tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
12253 		ColorSplitButton : function(id, s, ed) {
12254 			var t = this;
12255 
12256 			t.parent(id, s, ed);
12257 
12258 			t.settings = s = tinymce.extend({
12259 				colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
12260 				grid_width : 8,
12261 				default_color : '#888888'
12262 			}, t.settings);
12263 
12264 			t.onShowMenu = new tinymce.util.Dispatcher(t);
12265 
12266 			t.onHideMenu = new tinymce.util.Dispatcher(t);
12267 
12268 			t.value = s.default_color;
12269 		},
12270 
12271 		showMenu : function() {
12272 			var t = this, r, p, e, p2;
12273 
12274 			if (t.isDisabled())
12275 				return;
12276 
12277 			if (!t.isMenuRendered) {
12278 				t.renderMenu();
12279 				t.isMenuRendered = true;
12280 			}
12281 
12282 			if (t.isMenuVisible)
12283 				return t.hideMenu();
12284 
12285 			e = DOM.get(t.id);
12286 			DOM.show(t.id + '_menu');
12287 			DOM.addClass(e, 'mceSplitButtonSelected');
12288 			p2 = DOM.getPos(e);
12289 			DOM.setStyles(t.id + '_menu', {
12290 				left : p2.x,
12291 				top : p2.y + e.firstChild.clientHeight,
12292 				zIndex : 200000
12293 			});
12294 			e = 0;
12295 
12296 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12297 			t.onShowMenu.dispatch(t);
12298 
12299 			if (t._focused) {
12300 				t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
12301 					if (e.keyCode == 27)
12302 						t.hideMenu();
12303 				});
12304 
12305 				DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
12306 			}
12307 
12308 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
12309 				root: t.id + '_menu',
12310 				items: DOM.select('a', t.id + '_menu'),
12311 				onCancel: function() {
12312 					t.hideMenu();
12313 					t.focus();
12314 				}
12315 			});
12316 
12317 			t.keyboardNav.focus();
12318 			t.isMenuVisible = 1;
12319 		},
12320 
12321 		hideMenu : function(e) {
12322 			var t = this;
12323 
12324 			if (t.isMenuVisible) {
12325 				// Prevent double toogles by canceling the mouse click event to the button
12326 				if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
12327 					return;
12328 
12329 				if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
12330 					DOM.removeClass(t.id, 'mceSplitButtonSelected');
12331 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12332 					Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
12333 					DOM.hide(t.id + '_menu');
12334 				}
12335 
12336 				t.isMenuVisible = 0;
12337 				t.onHideMenu.dispatch();
12338 				t.keyboardNav.destroy();
12339 			}
12340 		},
12341 
12342 		renderMenu : function() {
12343 			var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
12344 
12345 			w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
12346 			m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
12347 			DOM.add(m, 'span', {'class' : 'mceMenuLine'});
12348 
12349 			n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
12350 			tb = DOM.add(n, 'tbody');
12351 
12352 			// Generate color grid
12353 			i = 0;
12354 			each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
12355 				c = c.replace(/^#/, '');
12356 
12357 				if (!i--) {
12358 					tr = DOM.add(tb, 'tr');
12359 					i = s.grid_width - 1;
12360 				}
12361 
12362 				n = DOM.add(tr, 'td');
12363 				var settings = {
12364 					href : 'javascript:;',
12365 					style : {
12366 						backgroundColor : '#' + c
12367 					},
12368 					'title': t.editor.getLang('colors.' + c, c),
12369 					'data-mce-color' : '#' + c
12370 				};
12371 
12372 				// adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.
12373 				if (!tinymce.isIE ) {
12374 					settings.role = 'option';
12375 				}
12376 
12377 				n = DOM.add(n, 'a', settings);
12378 
12379 				if (t.editor.forcedHighContrastMode) {
12380 					n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
12381 					if (n.getContext && (context = n.getContext("2d"))) {
12382 						context.fillStyle = '#' + c;
12383 						context.fillRect(0, 0, 16, 16);
12384 					} else {
12385 						// No point leaving a canvas element around if it's not supported for drawing on anyway.
12386 						DOM.remove(n);
12387 					}
12388 				}
12389 			});
12390 
12391 			if (s.more_colors_func) {
12392 				n = DOM.add(tb, 'tr');
12393 				n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
12394 				n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
12395 
12396 				Event.add(n, 'click', function(e) {
12397 					s.more_colors_func.call(s.more_colors_scope || this);
12398 					return Event.cancel(e); // Cancel to fix onbeforeunload problem
12399 				});
12400 			}
12401 
12402 			DOM.addClass(m, 'mceColorSplitMenu');
12403 
12404 			// Prevent IE from scrolling and hindering click to occur #4019
12405 			Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
12406 
12407 			Event.add(t.id + '_menu', 'click', function(e) {
12408 				var c;
12409 
12410 				e = DOM.getParent(e.target, 'a', tb);
12411 
12412 				if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
12413 					t.setColor(c);
12414 
12415 				return false; // Prevent IE auto save warning
12416 			});
12417 
12418 			return w;
12419 		},
12420 
12421 		setColor : function(c) {
12422 			this.displayColor(c);
12423 			this.hideMenu();
12424 			this.settings.onselect(c);
12425 		},
12426 		
12427 		displayColor : function(c) {
12428 			var t = this;
12429 
12430 			DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
12431 
12432 			t.value = c;
12433 		},
12434 
12435 		postRender : function() {
12436 			var t = this, id = t.id;
12437 
12438 			t.parent();
12439 			DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
12440 			DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
12441 		},
12442 
12443 		destroy : function() {
12444 			var self = this;
12445 
12446 			self.parent();
12447 
12448 			Event.clear(self.id + '_menu');
12449 			Event.clear(self.id + '_more');
12450 			DOM.remove(self.id + '_menu');
12451 
12452 			if (self.keyboardNav) {
12453 				self.keyboardNav.destroy();
12454 			}
12455 		}
12456 	});
12457 })(tinymce);
12458 
12459 (function(tinymce) {
12460 // Shorten class names
12461 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
12462 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
12463 	renderHTML : function() {
12464 		var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
12465 
12466 		h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
12467 		//TODO: ACC test this out - adding a role = application for getting the landmarks working well.
12468 		h.push("<span role='application'>");
12469 		h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
12470 		each(controls, function(toolbar) {
12471 			h.push(toolbar.renderHTML());
12472 		});
12473 		h.push("</span>");
12474 		h.push('</div>');
12475 
12476 		return h.join('');
12477 	},
12478 	
12479 	focus : function() {
12480 		var t = this;
12481 		dom.get(t.id).focus();
12482 	},
12483 	
12484 	postRender : function() {
12485 		var t = this, items = [];
12486 
12487 		each(t.controls, function(toolbar) {
12488 			each (toolbar.controls, function(control) {
12489 				if (control.id) {
12490 					items.push(control);
12491 				}
12492 			});
12493 		});
12494 
12495 		t.keyNav = new tinymce.ui.KeyboardNavigation({
12496 			root: t.id,
12497 			items: items,
12498 			onCancel: function() {
12499 				//Move focus if webkit so that navigation back will read the item.
12500 				if (tinymce.isWebKit) {
12501 					dom.get(t.editor.id+"_ifr").focus();
12502 				}
12503 				t.editor.focus();
12504 			},
12505 			excludeFromTabOrder: !t.settings.tab_focus_toolbar
12506 		});
12507 	},
12508 	
12509 	destroy : function() {
12510 		var self = this;
12511 
12512 		self.parent();
12513 		self.keyNav.destroy();
12514 		Event.clear(self.id);
12515 	}
12516 });
12517 })(tinymce);
12518 
12519 (function(tinymce) {
12520 // Shorten class names
12521 var dom = tinymce.DOM, each = tinymce.each;
12522 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12523 	renderHTML : function() {
12524 		var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
12525 
12526 		cl = t.controls;
12527 		for (i=0; i<cl.length; i++) {
12528 			// Get current control, prev control, next control and if the control is a list box or not
12529 			co = cl[i];
12530 			pr = cl[i - 1];
12531 			nx = cl[i + 1];
12532 
12533 			// Add toolbar start
12534 			if (i === 0) {
12535 				c = 'mceToolbarStart';
12536 
12537 				if (co.Button)
12538 					c += ' mceToolbarStartButton';
12539 				else if (co.SplitButton)
12540 					c += ' mceToolbarStartSplitButton';
12541 				else if (co.ListBox)
12542 					c += ' mceToolbarStartListBox';
12543 
12544 				h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12545 			}
12546 
12547 			// Add toolbar end before list box and after the previous button
12548 			// This is to fix the o2k7 editor skins
12549 			if (pr && co.ListBox) {
12550 				if (pr.Button || pr.SplitButton)
12551 					h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
12552 			}
12553 
12554 			// Render control HTML
12555 
12556 			// IE 8 quick fix, needed to propertly generate a hit area for anchors
12557 			if (dom.stdMode)
12558 				h += '<td style="position: relative">' + co.renderHTML() + '</td>';
12559 			else
12560 				h += '<td>' + co.renderHTML() + '</td>';
12561 
12562 			// Add toolbar start after list box and before the next button
12563 			// This is to fix the o2k7 editor skins
12564 			if (nx && co.ListBox) {
12565 				if (nx.Button || nx.SplitButton)
12566 					h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
12567 			}
12568 		}
12569 
12570 		c = 'mceToolbarEnd';
12571 
12572 		if (co.Button)
12573 			c += ' mceToolbarEndButton';
12574 		else if (co.SplitButton)
12575 			c += ' mceToolbarEndSplitButton';
12576 		else if (co.ListBox)
12577 			c += ' mceToolbarEndListBox';
12578 
12579 		h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12580 
12581 		return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
12582 	}
12583 });
12584 })(tinymce);
12585 
12586 (function(tinymce) {
12587 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
12588 
12589 	tinymce.create('tinymce.AddOnManager', {
12590 		AddOnManager : function() {
12591 			var self = this;
12592 
12593 			self.items = [];
12594 			self.urls = {};
12595 			self.lookup = {};
12596 			self.onAdd = new Dispatcher(self);
12597 		},
12598 
12599 		get : function(n) {
12600 			if (this.lookup[n]) {
12601 				return this.lookup[n].instance;
12602 			} else {
12603 				return undefined;
12604 			}
12605 		},
12606 
12607 		dependencies : function(n) {
12608 			var result;
12609 			if (this.lookup[n]) {
12610 				result = this.lookup[n].dependencies;
12611 			}
12612 			return result || [];
12613 		},
12614 
12615 		requireLangPack : function(n) {
12616 			var s = tinymce.settings;
12617 
12618 			if (s && s.language && s.language_load !== false)
12619 				tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
12620 		},
12621 
12622 		add : function(id, o, dependencies) {
12623 			this.items.push(o);
12624 			this.lookup[id] = {instance:o, dependencies:dependencies};
12625 			this.onAdd.dispatch(this, id, o);
12626 
12627 			return o;
12628 		},
12629 		createUrl: function(baseUrl, dep) {
12630 			if (typeof dep === "object") {
12631 				return dep
12632 			} else {
12633 				return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
12634 			}
12635 		},
12636 
12637 		addComponents: function(pluginName, scripts) {
12638 			var pluginUrl = this.urls[pluginName];
12639 			tinymce.each(scripts, function(script){
12640 				tinymce.ScriptLoader.add(pluginUrl+"/"+script);	
12641 			});
12642 		},
12643 
12644 		load : function(n, u, cb, s) {
12645 			var t = this, url = u;
12646 
12647 			function loadDependencies() {
12648 				var dependencies = t.dependencies(n);
12649 				tinymce.each(dependencies, function(dep) {
12650 					var newUrl = t.createUrl(u, dep);
12651 					t.load(newUrl.resource, newUrl, undefined, undefined);
12652 				});
12653 				if (cb) {
12654 					if (s) {
12655 						cb.call(s);
12656 					} else {
12657 						cb.call(tinymce.ScriptLoader);
12658 					}
12659 				}
12660 			}
12661 
12662 			if (t.urls[n])
12663 				return;
12664 			if (typeof u === "object")
12665 				url = u.prefix + u.resource + u.suffix;
12666 
12667 			if (url.indexOf('/') !== 0 && url.indexOf('://') == -1)
12668 				url = tinymce.baseURL + '/' + url;
12669 
12670 			t.urls[n] = url.substring(0, url.lastIndexOf('/'));
12671 
12672 			if (t.lookup[n]) {
12673 				loadDependencies();
12674 			} else {
12675 				tinymce.ScriptLoader.add(url, loadDependencies, s);
12676 			}
12677 		}
12678 	});
12679 
12680 	// Create plugin and theme managers
12681 	tinymce.PluginManager = new tinymce.AddOnManager();
12682 	tinymce.ThemeManager = new tinymce.AddOnManager();
12683 }(tinymce));
12684 
12685 (function(tinymce) {
12686 	// Shorten names
12687 	var each = tinymce.each, extend = tinymce.extend,
12688 		DOM = tinymce.DOM, Event = tinymce.dom.Event,
12689 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
12690 		explode = tinymce.explode,
12691 		Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0;
12692 
12693 	// Setup some URLs where the editor API is located and where the document is
12694 	tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
12695 	if (!/[\/\\]$/.test(tinymce.documentBaseURL))
12696 		tinymce.documentBaseURL += '/';
12697 
12698 	tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
12699 
12700 	tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
12701 
12702 	// Add before unload listener
12703 	// This was required since IE was leaking memory if you added and removed beforeunload listeners
12704 	// with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
12705 	tinymce.onBeforeUnload = new Dispatcher(tinymce);
12706 
12707 	// Must be on window or IE will leak if the editor is placed in frame or iframe
12708 	Event.add(window, 'beforeunload', function(e) {
12709 		tinymce.onBeforeUnload.dispatch(tinymce, e);
12710 	});
12711 
12712 	tinymce.onAddEditor = new Dispatcher(tinymce);
12713 
12714 	tinymce.onRemoveEditor = new Dispatcher(tinymce);
12715 
12716 	tinymce.EditorManager = extend(tinymce, {
12717 		editors : [],
12718 
12719 		i18n : {},
12720 
12721 		activeEditor : null,
12722 
12723 		init : function(s) {
12724 			var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
12725 
12726 			function createId(elm) {
12727 				var id = elm.id;
12728 	
12729 				// Use element id, or unique name or generate a unique id
12730 				if (!id) {
12731 					id = elm.name;
12732 	
12733 					if (id && !DOM.get(id)) {
12734 						id = elm.name;
12735 					} else {
12736 						// Generate unique name
12737 						id = DOM.uniqueId();
12738 					}
12739 
12740 					elm.setAttribute('id', id);
12741 				}
12742 
12743 				return id;
12744 			};
12745 
12746 			function execCallback(se, n, s) {
12747 				var f = se[n];
12748 
12749 				if (!f)
12750 					return;
12751 
12752 				if (tinymce.is(f, 'string')) {
12753 					s = f.replace(/\.\w+$/, '');
12754 					s = s ? tinymce.resolve(s) : 0;
12755 					f = tinymce.resolve(f);
12756 				}
12757 
12758 				return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
12759 			};
12760 
12761 			function hasClass(n, c) {
12762 				return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
12763 			};
12764 
12765 			t.settings = s;
12766 
12767 			// Legacy call
12768 			Event.bind(window, 'ready', function() {
12769 				var l, co;
12770 
12771 				execCallback(s, 'onpageload');
12772 
12773 				switch (s.mode) {
12774 					case "exact":
12775 						l = s.elements || '';
12776 
12777 						if(l.length > 0) {
12778 							each(explode(l), function(v) {
12779 								if (DOM.get(v)) {
12780 									ed = new tinymce.Editor(v, s);
12781 									el.push(ed);
12782 									ed.render(1);
12783 								} else {
12784 									each(document.forms, function(f) {
12785 										each(f.elements, function(e) {
12786 											if (e.name === v) {
12787 												v = 'mce_editor_' + instanceCounter++;
12788 												DOM.setAttrib(e, 'id', v);
12789 
12790 												ed = new tinymce.Editor(v, s);
12791 												el.push(ed);
12792 												ed.render(1);
12793 											}
12794 										});
12795 									});
12796 								}
12797 							});
12798 						}
12799 						break;
12800 
12801 					case "textareas":
12802 					case "specific_textareas":
12803 						each(DOM.select('textarea'), function(elm) {
12804 							if (s.editor_deselector && hasClass(elm, s.editor_deselector))
12805 								return;
12806 
12807 							if (!s.editor_selector || hasClass(elm, s.editor_selector)) {
12808 								ed = new tinymce.Editor(createId(elm), s);
12809 								el.push(ed);
12810 								ed.render(1);
12811 							}
12812 						});
12813 						break;
12814 					
12815 					default:
12816 						if (s.types) {
12817 							// Process type specific selector
12818 							each(s.types, function(type) {
12819 								each(DOM.select(type.selector), function(elm) {
12820 									var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type));
12821 									el.push(editor);
12822 									editor.render(1);
12823 								});
12824 							});
12825 						} else if (s.selector) {
12826 							// Process global selector
12827 							each(DOM.select(s.selector), function(elm) {
12828 								var editor = new tinymce.Editor(createId(elm), s);
12829 								el.push(editor);
12830 								editor.render(1);
12831 							});
12832 						}
12833 				}
12834 
12835 				// Call onInit when all editors are initialized
12836 				if (s.oninit) {
12837 					l = co = 0;
12838 
12839 					each(el, function(ed) {
12840 						co++;
12841 
12842 						if (!ed.initialized) {
12843 							// Wait for it
12844 							ed.onInit.add(function() {
12845 								l++;
12846 
12847 								// All done
12848 								if (l == co)
12849 									execCallback(s, 'oninit');
12850 							});
12851 						} else
12852 							l++;
12853 
12854 						// All done
12855 						if (l == co)
12856 							execCallback(s, 'oninit');					
12857 					});
12858 				}
12859 			});
12860 		},
12861 
12862 		get : function(id) {
12863 			if (id === undef)
12864 				return this.editors;
12865 
12866 			return this.editors[id];
12867 		},
12868 
12869 		getInstanceById : function(id) {
12870 			return this.get(id);
12871 		},
12872 
12873 		add : function(editor) {
12874 			var self = this, editors = self.editors;
12875 
12876 			// Add named and index editor instance
12877 			editors[editor.id] = editor;
12878 			editors.push(editor);
12879 
12880 			self._setActive(editor);
12881 			self.onAddEditor.dispatch(self, editor);
12882 
12883 
12884 			return editor;
12885 		},
12886 
12887 		remove : function(editor) {
12888 			var t = this, i, editors = t.editors;
12889 
12890 			// Not in the collection
12891 			if (!editors[editor.id])
12892 				return null;
12893 
12894 			delete editors[editor.id];
12895 
12896 			for (i = 0; i < editors.length; i++) {
12897 				if (editors[i] == editor) {
12898 					editors.splice(i, 1);
12899 					break;
12900 				}
12901 			}
12902 
12903 			// Select another editor since the active one was removed
12904 			if (t.activeEditor == editor)
12905 				t._setActive(editors[0]);
12906 
12907 			editor.destroy();
12908 			t.onRemoveEditor.dispatch(t, editor);
12909 
12910 			return editor;
12911 		},
12912 
12913 		execCommand : function(c, u, v) {
12914 			var t = this, ed = t.get(v), w;
12915 
12916 			function clr() {
12917 				ed.destroy();
12918 				w.detachEvent('onunload', clr);
12919 				w = w.tinyMCE = w.tinymce = null; // IE leak
12920 			};
12921 
12922 			// Manager commands
12923 			switch (c) {
12924 				case "mceFocus":
12925 					ed.focus();
12926 					return true;
12927 
12928 				case "mceAddEditor":
12929 				case "mceAddControl":
12930 					if (!t.get(v))
12931 						new tinymce.Editor(v, t.settings).render();
12932 
12933 					return true;
12934 
12935 				case "mceAddFrameControl":
12936 					w = v.window;
12937 
12938 					// Add tinyMCE global instance and tinymce namespace to specified window
12939 					w.tinyMCE = tinyMCE;
12940 					w.tinymce = tinymce;
12941 
12942 					tinymce.DOM.doc = w.document;
12943 					tinymce.DOM.win = w;
12944 
12945 					ed = new tinymce.Editor(v.element_id, v);
12946 					ed.render();
12947 
12948 					// Fix IE memory leaks
12949 					if (tinymce.isIE) {
12950 						w.attachEvent('onunload', clr);
12951 					}
12952 
12953 					v.page_window = null;
12954 
12955 					return true;
12956 
12957 				case "mceRemoveEditor":
12958 				case "mceRemoveControl":
12959 					if (ed)
12960 						ed.remove();
12961 
12962 					return true;
12963 
12964 				case 'mceToggleEditor':
12965 					if (!ed) {
12966 						t.execCommand('mceAddControl', 0, v);
12967 						return true;
12968 					}
12969 
12970 					if (ed.isHidden())
12971 						ed.show();
12972 					else
12973 						ed.hide();
12974 
12975 					return true;
12976 			}
12977 
12978 			// Run command on active editor
12979 			if (t.activeEditor)
12980 				return t.activeEditor.execCommand(c, u, v);
12981 
12982 			return false;
12983 		},
12984 
12985 		execInstanceCommand : function(id, c, u, v) {
12986 			var ed = this.get(id);
12987 
12988 			if (ed)
12989 				return ed.execCommand(c, u, v);
12990 
12991 			return false;
12992 		},
12993 
12994 		triggerSave : function() {
12995 			each(this.editors, function(e) {
12996 				e.save();
12997 			});
12998 		},
12999 
13000 		addI18n : function(p, o) {
13001 			var lo, i18n = this.i18n;
13002 
13003 			if (!tinymce.is(p, 'string')) {
13004 				each(p, function(o, lc) {
13005 					each(o, function(o, g) {
13006 						each(o, function(o, k) {
13007 							if (g === 'common')
13008 								i18n[lc + '.' + k] = o;
13009 							else
13010 								i18n[lc + '.' + g + '.' + k] = o;
13011 						});
13012 					});
13013 				});
13014 			} else {
13015 				each(o, function(o, k) {
13016 					i18n[p + '.' + k] = o;
13017 				});
13018 			}
13019 		},
13020 
13021 		// Private methods
13022 
13023 		_setActive : function(editor) {
13024 			this.selectedInstance = this.activeEditor = editor;
13025 		}
13026 	});
13027 })(tinymce);
13028 
13029 (function(tinymce) {
13030 	// Shorten these names
13031 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
13032 		each = tinymce.each, isGecko = tinymce.isGecko,
13033 		isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
13034 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
13035 		explode = tinymce.explode;
13036 
13037 	tinymce.create('tinymce.Editor', {
13038 		Editor : function(id, settings) {
13039 			var self = this, TRUE = true;
13040 
13041 			self.settings = settings = extend({
13042 				id : id,
13043 				language : 'en',
13044 				theme : 'advanced',
13045 				skin : 'default',
13046 				delta_width : 0,
13047 				delta_height : 0,
13048 				popup_css : '',
13049 				plugins : '',
13050 				document_base_url : tinymce.documentBaseURL,
13051 				add_form_submit_trigger : TRUE,
13052 				submit_patch : TRUE,
13053 				add_unload_trigger : TRUE,
13054 				convert_urls : TRUE,
13055 				relative_urls : TRUE,
13056 				remove_script_host : TRUE,
13057 				table_inline_editing : false,
13058 				object_resizing : TRUE,
13059 				accessibility_focus : TRUE,
13060 				doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
13061 				visual : TRUE,
13062 				font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
13063 				font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
13064 				apply_source_formatting : TRUE,
13065 				directionality : 'ltr',
13066 				forced_root_block : 'p',
13067 				hidden_input : TRUE,
13068 				padd_empty_editor : TRUE,
13069 				render_ui : TRUE,
13070 				indentation : '30px',
13071 				fix_table_elements : TRUE,
13072 				inline_styles : TRUE,
13073 				convert_fonts_to_spans : TRUE,
13074 				indent : 'simple',
13075 				indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
13076 				indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
13077 				validate : TRUE,
13078 				entity_encoding : 'named',
13079 				url_converter : self.convertURL,
13080 				url_converter_scope : self,
13081 				ie7_compat : TRUE
13082 			}, settings);
13083 
13084 			self.id = self.editorId = id;
13085 
13086 			self.isNotDirty = false;
13087 
13088 			self.plugins = {};
13089 
13090 			self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, {
13091 				base_uri : tinyMCE.baseURI
13092 			});
13093 
13094 			self.baseURI = tinymce.baseURI;
13095 
13096 			self.contentCSS = [];
13097 
13098 			self.contentStyles = [];
13099 
13100 			// Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
13101 			self.setupEvents();
13102 
13103 			// Internal command handler objects
13104 			self.execCommands = {};
13105 			self.queryStateCommands = {};
13106 			self.queryValueCommands = {};
13107 
13108 			// Call setup
13109 			self.execCallback('setup', self);
13110 		},
13111 
13112 		render : function(nst) {
13113 			var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
13114 
13115 			// Page is not loaded yet, wait for it
13116 			if (!Event.domLoaded) {
13117 				Event.add(window, 'ready', function() {
13118 					t.render();
13119 				});
13120 				return;
13121 			}
13122 
13123 			tinyMCE.settings = s;
13124 
13125 			// Element not found, then skip initialization
13126 			if (!t.getElement())
13127 				return;
13128 
13129 			// Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 
13130 			// here since the browser says it has contentEditable support but there is no visible caret.
13131 			if (tinymce.isIDevice && !tinymce.isIOS5)
13132 				return;
13133 
13134 			// Add hidden input for non input elements inside form elements
13135 			if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
13136 				DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
13137 
13138 			// Hide target element early to prevent content flashing
13139 			if (!s.content_editable) {
13140 				t.orgVisibility = t.getElement().style.visibility;
13141 				t.getElement().style.visibility = 'hidden';
13142 			}
13143 
13144 			if (tinymce.WindowManager)
13145 				t.windowManager = new tinymce.WindowManager(t);
13146 
13147 			if (s.encoding == 'xml') {
13148 				t.onGetContent.add(function(ed, o) {
13149 					if (o.save)
13150 						o.content = DOM.encode(o.content);
13151 				});
13152 			}
13153 
13154 			if (s.add_form_submit_trigger) {
13155 				t.onSubmit.addToTop(function() {
13156 					if (t.initialized) {
13157 						t.save();
13158 						t.isNotDirty = 1;
13159 					}
13160 				});
13161 			}
13162 
13163 			if (s.add_unload_trigger) {
13164 				t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
13165 					if (t.initialized && !t.destroyed && !t.isHidden())
13166 						t.save({format : 'raw', no_events : true});
13167 				});
13168 			}
13169 
13170 			tinymce.addUnload(t.destroy, t);
13171 
13172 			if (s.submit_patch) {
13173 				t.onBeforeRenderUI.add(function() {
13174 					var n = t.getElement().form;
13175 
13176 					if (!n)
13177 						return;
13178 
13179 					// Already patched
13180 					if (n._mceOldSubmit)
13181 						return;
13182 
13183 					// Check page uses id="submit" or name="submit" for it's submit button
13184 					if (!n.submit.nodeType && !n.submit.length) {
13185 						t.formElement = n;
13186 						n._mceOldSubmit = n.submit;
13187 						n.submit = function() {
13188 							// Save all instances
13189 							tinymce.triggerSave();
13190 							t.isNotDirty = 1;
13191 
13192 							return t.formElement._mceOldSubmit(t.formElement);
13193 						};
13194 					}
13195 
13196 					n = null;
13197 				});
13198 			}
13199 
13200 			// Load scripts
13201 			function loadScripts() {
13202 				if (s.language && s.language_load !== false)
13203 					sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
13204 
13205 				if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
13206 					ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
13207 
13208 				each(explode(s.plugins), function(p) {
13209 					if (p &&!PluginManager.urls[p]) {
13210 						if (p.charAt(0) == '-') {
13211 							p = p.substr(1, p.length);
13212 							var dependencies = PluginManager.dependencies(p);
13213 							each(dependencies, function(dep) {
13214 								var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
13215 								dep = PluginManager.createUrl(defaultSettings, dep);
13216 								PluginManager.load(dep.resource, dep);
13217 							});
13218 						} else {
13219 							// Skip safari plugin, since it is removed as of 3.3b1
13220 							if (p == 'safari') {
13221 								return;
13222 							}
13223 							PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
13224 						}
13225 					}
13226 				});
13227 
13228 				// Init when que is loaded
13229 				sl.loadQueue(function() {
13230 					if (!t.removed)
13231 						t.init();
13232 				});
13233 			};
13234 
13235 			loadScripts();
13236 		},
13237 
13238 		init : function() {
13239 			var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
13240 
13241 			tinymce.add(t);
13242 
13243 			s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
13244 
13245 			if (s.theme) {
13246 				if (typeof s.theme != "function") {
13247 					s.theme = s.theme.replace(/-/, '');
13248 					o = ThemeManager.get(s.theme);
13249 					t.theme = new o();
13250 
13251 					if (t.theme.init)
13252 						t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
13253 				} else {
13254 					t.theme = s.theme;
13255 				}
13256 			}
13257 
13258 			function initPlugin(p) {
13259 				var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
13260 				if (c && tinymce.inArray(initializedPlugins,p) === -1) {
13261 					each(PluginManager.dependencies(p), function(dep){
13262 						initPlugin(dep);
13263 					});
13264 					po = new c(t, u);
13265 
13266 					t.plugins[p] = po;
13267 
13268 					if (po.init) {
13269 						po.init(t, u);
13270 						initializedPlugins.push(p);
13271 					}
13272 				}
13273 			}
13274 			
13275 			// Create all plugins
13276 			each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
13277 
13278 			// Setup popup CSS path(s)
13279 			if (s.popup_css !== false) {
13280 				if (s.popup_css)
13281 					s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
13282 				else
13283 					s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
13284 			}
13285 
13286 			if (s.popup_css_add)
13287 				s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
13288 
13289 			t.controlManager = new tinymce.ControlManager(t);
13290 
13291 			// Enables users to override the control factory
13292 			t.onBeforeRenderUI.dispatch(t, t.controlManager);
13293 
13294 			// Measure box
13295 			if (s.render_ui && t.theme) {
13296 				t.orgDisplay = e.style.display;
13297 
13298 				if (typeof s.theme != "function") {
13299 					w = s.width || e.style.width || e.offsetWidth;
13300 					h = s.height || e.style.height || e.offsetHeight;
13301 					mh = s.min_height || 100;
13302 					re = /^[0-9\.]+(|px)$/i;
13303 
13304 					if (re.test('' + w))
13305 						w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
13306 
13307 					if (re.test('' + h))
13308 						h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh);
13309 
13310 					// Render UI
13311 					o = t.theme.renderUI({
13312 						targetNode : e,
13313 						width : w,
13314 						height : h,
13315 						deltaWidth : s.delta_width,
13316 						deltaHeight : s.delta_height
13317 					});
13318 
13319 					// Resize editor
13320 					DOM.setStyles(o.sizeContainer || o.editorContainer, {
13321 						width : w,
13322 						height : h
13323 					});
13324 
13325 					h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
13326 					if (h < mh)
13327 						h = mh;
13328 				} else {
13329 					o = s.theme(t, e);
13330 
13331 					// Convert element type to id:s
13332 					if (o.editorContainer.nodeType) {
13333 						o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent";
13334 					}
13335 
13336 					// Convert element type to id:s
13337 					if (o.iframeContainer.nodeType) {
13338 						o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer";
13339 					}
13340 
13341 					// Use specified iframe height or the targets offsetHeight
13342 					h = o.iframeHeight || e.offsetHeight;
13343 
13344 					// Store away the selection when it's changed to it can be restored later with a editor.focus() call
13345 					if (isIE) {
13346 						t.onInit.add(function(ed) {
13347 							ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() {
13348 								ed.lastIERng = ed.selection.getRng();
13349 							});
13350 						});
13351 					}
13352 				}
13353 
13354 				t.editorContainer = o.editorContainer;
13355 			}
13356 
13357 			// Load specified content CSS last
13358 			if (s.content_css) {
13359 				each(explode(s.content_css), function(u) {
13360 					t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
13361 				});
13362 			}
13363 
13364 			// Content editable mode ends here
13365 			if (s.content_editable) {
13366 				e = n = o = null; // Fix IE leak
13367 				return t.initContentBody();
13368 			}
13369 
13370 			// User specified a document.domain value
13371 			if (document.domain && location.hostname != document.domain)
13372 				tinymce.relaxedDomain = document.domain;
13373 
13374 			t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
13375 
13376 			// We only need to override paths if we have to
13377 			// IE has a bug where it remove site absolute urls to relative ones if this is specified
13378 			if (s.document_base_url != tinymce.documentBaseURL)
13379 				t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
13380 
13381 			// IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
13382 			if (s.ie7_compat)
13383 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
13384 			else
13385 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
13386 
13387 			t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
13388 
13389 			// Load the CSS by injecting them into the HTML this will reduce "flicker"
13390 			for (i = 0; i < t.contentCSS.length; i++) {
13391 				t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
13392 			}
13393 
13394 			t.contentCSS = [];
13395 
13396 			bi = s.body_id || 'tinymce';
13397 			if (bi.indexOf('=') != -1) {
13398 				bi = t.getParam('body_id', '', 'hash');
13399 				bi = bi[t.id] || bi;
13400 			}
13401 
13402 			bc = s.body_class || '';
13403 			if (bc.indexOf('=') != -1) {
13404 				bc = t.getParam('body_class', '', 'hash');
13405 				bc = bc[t.id] || '';
13406 			}
13407 
13408 			t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';
13409 
13410 			// Domain relaxing enabled, then set document domain
13411 			if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
13412 				// We need to write the contents here in IE since multiple writes messes up refresh button and back button
13413 				u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()';
13414 			}
13415 
13416 			// Create iframe
13417 			// TODO: ACC add the appropriate description on this.
13418 			n = DOM.add(o.iframeContainer, 'iframe', { 
13419 				id : t.id + "_ifr",
13420 				src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
13421 				frameBorder : '0',
13422 				allowTransparency : "true",
13423 				title : s.aria_label,
13424 				style : {
13425 					width : '100%',
13426 					height : h,
13427 					display : 'block' // Important for Gecko to render the iframe correctly
13428 				}
13429 			});
13430 
13431 			t.contentAreaContainer = o.iframeContainer;
13432 
13433 			if (o.editorContainer) {
13434 				DOM.get(o.editorContainer).style.display = t.orgDisplay;
13435 			}
13436 
13437 			// Restore visibility on target element
13438 			e.style.visibility = t.orgVisibility;
13439 
13440 			DOM.get(t.id).style.display = 'none';
13441 			DOM.setAttrib(t.id, 'aria-hidden', true);
13442 
13443 			if (!tinymce.relaxedDomain || !u)
13444 				t.initContentBody();
13445 
13446 			e = n = o = null; // Cleanup
13447 		},
13448 
13449 		initContentBody : function() {
13450 			var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText;
13451 
13452 			// Setup iframe body
13453 			if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) {
13454 				doc.open();
13455 				doc.write(self.iframeHTML);
13456 				doc.close();
13457 
13458 				if (tinymce.relaxedDomain)
13459 					doc.domain = tinymce.relaxedDomain;
13460 			}
13461 
13462 			if (settings.content_editable) {
13463 				DOM.addClass(targetElm, 'mceContentBody');
13464 				self.contentDocument = doc = settings.content_document || document;
13465 				self.contentWindow = settings.content_window || window;
13466 				self.bodyElement = targetElm;
13467 
13468 				// Prevent leak in IE
13469 				settings.content_document = settings.content_window = null;
13470 			}
13471 
13472 			// It will not steal focus while setting contentEditable
13473 			body = self.getBody();
13474 			body.disabled = true;
13475 
13476 			if (!settings.readonly)
13477 				body.contentEditable = self.getParam('content_editable_state', true);
13478 
13479 			body.disabled = false;
13480 
13481 			self.schema = new tinymce.html.Schema(settings);
13482 
13483 			self.dom = new tinymce.dom.DOMUtils(doc, {
13484 				keep_values : true,
13485 				url_converter : self.convertURL,
13486 				url_converter_scope : self,
13487 				hex_colors : settings.force_hex_style_colors,
13488 				class_filter : settings.class_filter,
13489 				update_styles : true,
13490 				root_element : settings.content_editable ? self.id : null,
13491 				schema : self.schema
13492 			});
13493 
13494 			self.parser = new tinymce.html.DomParser(settings, self.schema);
13495 
13496 			// Convert src and href into data-mce-src, data-mce-href and data-mce-style
13497 			self.parser.addAttributeFilter('src,href,style', function(nodes, name) {
13498 				var i = nodes.length, node, dom = self.dom, value, internalName;
13499 
13500 				while (i--) {
13501 					node = nodes[i];
13502 					value = node.attr(name);
13503 					internalName = 'data-mce-' + name;
13504 
13505 					// Add internal attribute if we need to we don't on a refresh of the document
13506 					if (!node.attributes.map[internalName]) {	
13507 						if (name === "style")
13508 							node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
13509 						else
13510 							node.attr(internalName, self.convertURL(value, name, node.name));
13511 					}
13512 				}
13513 			});
13514 
13515 			// Keep scripts from executing
13516 			self.parser.addNodeFilter('script', function(nodes, name) {
13517 				var i = nodes.length, node;
13518 
13519 				while (i--) {
13520 					node = nodes[i];
13521 					node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
13522 				}
13523 			});
13524 
13525 			self.parser.addNodeFilter('#cdata', function(nodes, name) {
13526 				var i = nodes.length, node;
13527 
13528 				while (i--) {
13529 					node = nodes[i];
13530 					node.type = 8;
13531 					node.name = '#comment';
13532 					node.value = '[CDATA[' + node.value + ']]';
13533 				}
13534 			});
13535 
13536 			self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
13537 				var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
13538 
13539 				while (i--) {
13540 					node = nodes[i];
13541 
13542 					if (node.isEmpty(nonEmptyElements))
13543 						node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
13544 				}
13545 			});
13546 
13547 			self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema);
13548 
13549 			self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self);
13550 
13551 			self.formatter = new tinymce.Formatter(self);
13552 
13553 			self.undoManager = new tinymce.UndoManager(self);
13554 
13555 			self.forceBlocks = new tinymce.ForceBlocks(self);
13556 			self.enterKey = new tinymce.EnterKey(self);
13557 			self.editorCommands = new tinymce.EditorCommands(self);
13558 
13559 			self.onExecCommand.add(function(editor, command) {
13560 				// Don't refresh the select lists until caret move
13561 				if (!/^(FontName|FontSize)$/.test(command))
13562 					self.nodeChanged();
13563 			});
13564 
13565 			// Pass through
13566 			self.serializer.onPreProcess.add(function(se, o) {
13567 				return self.onPreProcess.dispatch(self, o, se);
13568 			});
13569 
13570 			self.serializer.onPostProcess.add(function(se, o) {
13571 				return self.onPostProcess.dispatch(self, o, se);
13572 			});
13573 
13574 			self.onPreInit.dispatch(self);
13575 
13576 			if (!settings.browser_spellcheck && !settings.gecko_spellcheck)
13577 				doc.body.spellcheck = false;
13578 
13579 			if (!settings.readonly) {
13580 				self.bindNativeEvents();
13581 			}
13582 
13583 			self.controlManager.onPostRender.dispatch(self, self.controlManager);
13584 			self.onPostRender.dispatch(self);
13585 
13586 			self.quirks = tinymce.util.Quirks(self);
13587 
13588 			if (settings.directionality)
13589 				body.dir = settings.directionality;
13590 
13591 			if (settings.nowrap)
13592 				body.style.whiteSpace = "nowrap";
13593 
13594 			if (settings.protect) {
13595 				self.onBeforeSetContent.add(function(ed, o) {
13596 					each(settings.protect, function(pattern) {
13597 						o.content = o.content.replace(pattern, function(str) {
13598 							return '<!--mce:protected ' + escape(str) + '-->';
13599 						});
13600 					});
13601 				});
13602 			}
13603 
13604 			// Add visual aids when new contents is added
13605 			self.onSetContent.add(function() {
13606 				self.addVisual(self.getBody());
13607 			});
13608 
13609 			// Remove empty contents
13610 			if (settings.padd_empty_editor) {
13611 				self.onPostProcess.add(function(ed, o) {
13612 					o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
13613 				});
13614 			}
13615 
13616 			self.load({initial : true, format : 'html'});
13617 			self.startContent = self.getContent({format : 'raw'});
13618 
13619 			self.initialized = true;
13620 
13621 			self.onInit.dispatch(self);
13622 			self.execCallback('setupcontent_callback', self.id, body, doc);
13623 			self.execCallback('init_instance_callback', self);
13624 			self.focus(true);
13625 			self.nodeChanged({initial : true});
13626 
13627 			// Add editor specific CSS styles
13628 			if (self.contentStyles.length > 0) {
13629 				contentCssText = '';
13630 
13631 				each(self.contentStyles, function(style) {
13632 					contentCssText += style + "\r\n";
13633 				});
13634 
13635 				self.dom.addStyle(contentCssText);
13636 			}
13637 
13638 			// Load specified content CSS last
13639 			each(self.contentCSS, function(url) {
13640 				self.dom.loadCSS(url);
13641 			});
13642 
13643 			// Handle auto focus
13644 			if (settings.auto_focus) {
13645 				setTimeout(function () {
13646 					var ed = tinymce.get(settings.auto_focus);
13647 
13648 					ed.selection.select(ed.getBody(), 1);
13649 					ed.selection.collapse(1);
13650 					ed.getBody().focus();
13651 					ed.getWin().focus();
13652 				}, 100);
13653 			}
13654 
13655 			// Clean up references for IE
13656 			targetElm = doc = body = null;
13657 		},
13658 
13659 		focus : function(skip_focus) {
13660 			var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;
13661 
13662 			if (!skip_focus) {
13663 				if (self.lastIERng) {
13664 					selection.setRng(self.lastIERng);
13665 				}
13666 
13667 				// Get selected control element
13668 				ieRng = selection.getRng();
13669 				if (ieRng.item) {
13670 					controlElm = ieRng.item(0);
13671 				}
13672 
13673 				self._refreshContentEditable();
13674 
13675 				// Focus the window iframe
13676 				if (!contentEditable) {
13677 					self.getWin().focus();
13678 				}
13679 
13680 				// Focus the body as well since it's contentEditable
13681 				if (tinymce.isGecko || contentEditable) {
13682 					body = self.getBody();
13683 
13684 					// Check for setActive since it doesn't scroll to the element
13685 					if (body.setActive) {
13686 						body.setActive();
13687 					} else {
13688 						body.focus();
13689 					}
13690 
13691 					if (contentEditable) {
13692 						selection.normalize();
13693 					}
13694 				}
13695 
13696 				// Restore selected control element
13697 				// This is needed when for example an image is selected within a
13698 				// layer a call to focus will then remove the control selection
13699 				if (controlElm && controlElm.ownerDocument == doc) {
13700 					ieRng = doc.body.createControlRange();
13701 					ieRng.addElement(controlElm);
13702 					ieRng.select();
13703 				}
13704 			}
13705 
13706 			if (tinymce.activeEditor != self) {
13707 				if ((oed = tinymce.activeEditor) != null)
13708 					oed.onDeactivate.dispatch(oed, self);
13709 
13710 				self.onActivate.dispatch(self, oed);
13711 			}
13712 
13713 			tinymce._setActive(self);
13714 		},
13715 
13716 		execCallback : function(n) {
13717 			var t = this, f = t.settings[n], s;
13718 
13719 			if (!f)
13720 				return;
13721 
13722 			// Look through lookup
13723 			if (t.callbackLookup && (s = t.callbackLookup[n])) {
13724 				f = s.func;
13725 				s = s.scope;
13726 			}
13727 
13728 			if (is(f, 'string')) {
13729 				s = f.replace(/\.\w+$/, '');
13730 				s = s ? tinymce.resolve(s) : 0;
13731 				f = tinymce.resolve(f);
13732 				t.callbackLookup = t.callbackLookup || {};
13733 				t.callbackLookup[n] = {func : f, scope : s};
13734 			}
13735 
13736 			return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
13737 		},
13738 
13739 		translate : function(s) {
13740 			var c = this.settings.language || 'en', i18n = tinymce.i18n;
13741 
13742 			if (!s)
13743 				return '';
13744 
13745 			return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) {
13746 				return i18n[c + '.' + b] || '{#' + b + '}';
13747 			});
13748 		},
13749 
13750 		getLang : function(n, dv) {
13751 			return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
13752 		},
13753 
13754 		getParam : function(n, dv, ty) {
13755 			var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
13756 
13757 			if (ty === 'hash') {
13758 				o = {};
13759 
13760 				if (is(v, 'string')) {
13761 					each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
13762 						v = v.split('=');
13763 
13764 						if (v.length > 1)
13765 							o[tr(v[0])] = tr(v[1]);
13766 						else
13767 							o[tr(v[0])] = tr(v);
13768 					});
13769 				} else
13770 					o = v;
13771 
13772 				return o;
13773 			}
13774 
13775 			return v;
13776 		},
13777 
13778 		nodeChanged : function(o) {
13779 			var self = this, selection = self.selection, node;
13780 
13781 			// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
13782 			if (self.initialized) {
13783 				o = o || {};
13784 
13785 				// Get start node
13786 				node = selection.getStart() || self.getBody();
13787 				node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
13788 
13789 				// Get parents and add them to object
13790 				o.parents = [];
13791 				self.dom.getParent(node, function(node) {
13792 					if (node.nodeName == 'BODY')
13793 						return true;
13794 
13795 					o.parents.push(node);
13796 				});
13797 
13798 				self.onNodeChange.dispatch(
13799 					self,
13800 					o ? o.controlManager || self.controlManager : self.controlManager,
13801 					node,
13802 					selection.isCollapsed(),
13803 					o
13804 				);
13805 			}
13806 		},
13807 
13808 		addButton : function(name, settings) {
13809 			var self = this;
13810 
13811 			self.buttons = self.buttons || {};
13812 			self.buttons[name] = settings;
13813 		},
13814 
13815 		addCommand : function(name, callback, scope) {
13816 			this.execCommands[name] = {func : callback, scope : scope || this};
13817 		},
13818 
13819 		addQueryStateHandler : function(name, callback, scope) {
13820 			this.queryStateCommands[name] = {func : callback, scope : scope || this};
13821 		},
13822 
13823 		addQueryValueHandler : function(name, callback, scope) {
13824 			this.queryValueCommands[name] = {func : callback, scope : scope || this};
13825 		},
13826 
13827 		addShortcut : function(pa, desc, cmd_func, sc) {
13828 			var t = this, c;
13829 
13830 			if (t.settings.custom_shortcuts === false)
13831 				return false;
13832 
13833 			t.shortcuts = t.shortcuts || {};
13834 
13835 			if (is(cmd_func, 'string')) {
13836 				c = cmd_func;
13837 
13838 				cmd_func = function() {
13839 					t.execCommand(c, false, null);
13840 				};
13841 			}
13842 
13843 			if (is(cmd_func, 'object')) {
13844 				c = cmd_func;
13845 
13846 				cmd_func = function() {
13847 					t.execCommand(c[0], c[1], c[2]);
13848 				};
13849 			}
13850 
13851 			each(explode(pa), function(pa) {
13852 				var o = {
13853 					func : cmd_func,
13854 					scope : sc || this,
13855 					desc : t.translate(desc),
13856 					alt : false,
13857 					ctrl : false,
13858 					shift : false
13859 				};
13860 
13861 				each(explode(pa, '+'), function(v) {
13862 					switch (v) {
13863 						case 'alt':
13864 						case 'ctrl':
13865 						case 'shift':
13866 							o[v] = true;
13867 							break;
13868 
13869 						default:
13870 							o.charCode = v.charCodeAt(0);
13871 							o.keyCode = v.toUpperCase().charCodeAt(0);
13872 					}
13873 				});
13874 
13875 				t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
13876 			});
13877 
13878 			return true;
13879 		},
13880 
13881 		execCommand : function(cmd, ui, val, a) {
13882 			var t = this, s = 0, o, st;
13883 
13884 			if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
13885 				t.focus();
13886 
13887 			a = extend({}, a);
13888 			t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);
13889 			if (a.terminate)
13890 				return false;
13891 
13892 			// Command callback
13893 			if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
13894 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13895 				return true;
13896 			}
13897 
13898 			// Registred commands
13899 			if (o = t.execCommands[cmd]) {
13900 				st = o.func.call(o.scope, ui, val);
13901 
13902 				// Fall through on true
13903 				if (st !== true) {
13904 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
13905 					return st;
13906 				}
13907 			}
13908 
13909 			// Plugin commands
13910 			each(t.plugins, function(p) {
13911 				if (p.execCommand && p.execCommand(cmd, ui, val)) {
13912 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
13913 					s = 1;
13914 					return false;
13915 				}
13916 			});
13917 
13918 			if (s)
13919 				return true;
13920 
13921 			// Theme commands
13922 			if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
13923 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13924 				return true;
13925 			}
13926 
13927 			// Editor commands
13928 			if (t.editorCommands.execCommand(cmd, ui, val)) {
13929 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13930 				return true;
13931 			}
13932 
13933 			// Browser commands
13934 			t.getDoc().execCommand(cmd, ui, val);
13935 			t.onExecCommand.dispatch(t, cmd, ui, val, a);
13936 		},
13937 
13938 		queryCommandState : function(cmd) {
13939 			var t = this, o, s;
13940 
13941 			// Is hidden then return undefined
13942 			if (t._isHidden())
13943 				return;
13944 
13945 			// Registred commands
13946 			if (o = t.queryStateCommands[cmd]) {
13947 				s = o.func.call(o.scope);
13948 
13949 				// Fall though on true
13950 				if (s !== true)
13951 					return s;
13952 			}
13953 
13954 			// Registred commands
13955 			o = t.editorCommands.queryCommandState(cmd);
13956 			if (o !== -1)
13957 				return o;
13958 
13959 			// Browser commands
13960 			try {
13961 				return this.getDoc().queryCommandState(cmd);
13962 			} catch (ex) {
13963 				// Fails sometimes see bug: 1896577
13964 			}
13965 		},
13966 
13967 		queryCommandValue : function(c) {
13968 			var t = this, o, s;
13969 
13970 			// Is hidden then return undefined
13971 			if (t._isHidden())
13972 				return;
13973 
13974 			// Registred commands
13975 			if (o = t.queryValueCommands[c]) {
13976 				s = o.func.call(o.scope);
13977 
13978 				// Fall though on true
13979 				if (s !== true)
13980 					return s;
13981 			}
13982 
13983 			// Registred commands
13984 			o = t.editorCommands.queryCommandValue(c);
13985 			if (is(o))
13986 				return o;
13987 
13988 			// Browser commands
13989 			try {
13990 				return this.getDoc().queryCommandValue(c);
13991 			} catch (ex) {
13992 				// Fails sometimes see bug: 1896577
13993 			}
13994 		},
13995 
13996 		show : function() {
13997 			var self = this;
13998 
13999 			DOM.show(self.getContainer());
14000 			DOM.hide(self.id);
14001 			self.load();
14002 		},
14003 
14004 		hide : function() {
14005 			var self = this, doc = self.getDoc();
14006 
14007 			// Fixed bug where IE has a blinking cursor left from the editor
14008 			if (isIE && doc)
14009 				doc.execCommand('SelectAll');
14010 
14011 			// We must save before we hide so Safari doesn't crash
14012 			self.save();
14013 			DOM.hide(self.getContainer());
14014 			DOM.setStyle(self.id, 'display', self.orgDisplay);
14015 		},
14016 
14017 		isHidden : function() {
14018 			return !DOM.isHidden(this.id);
14019 		},
14020 
14021 		setProgressState : function(b, ti, o) {
14022 			this.onSetProgressState.dispatch(this, b, ti, o);
14023 
14024 			return b;
14025 		},
14026 
14027 		load : function(o) {
14028 			var t = this, e = t.getElement(), h;
14029 
14030 			if (e) {
14031 				o = o || {};
14032 				o.load = true;
14033 
14034 				// Double encode existing entities in the value
14035 				h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
14036 				o.element = e;
14037 
14038 				if (!o.no_events)
14039 					t.onLoadContent.dispatch(t, o);
14040 
14041 				o.element = e = null;
14042 
14043 				return h;
14044 			}
14045 		},
14046 
14047 		save : function(o) {
14048 			var t = this, e = t.getElement(), h, f;
14049 
14050 			if (!e || !t.initialized)
14051 				return;
14052 
14053 			o = o || {};
14054 			o.save = true;
14055 
14056 			o.element = e;
14057 			h = o.content = t.getContent(o);
14058 
14059 			if (!o.no_events)
14060 				t.onSaveContent.dispatch(t, o);
14061 
14062 			h = o.content;
14063 
14064 			if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
14065 				e.innerHTML = h;
14066 
14067 				// Update hidden form element
14068 				if (f = DOM.getParent(t.id, 'form')) {
14069 					each(f.elements, function(e) {
14070 						if (e.name == t.id) {
14071 							e.value = h;
14072 							return false;
14073 						}
14074 					});
14075 				}
14076 			} else
14077 				e.value = h;
14078 
14079 			o.element = e = null;
14080 
14081 			return h;
14082 		},
14083 
14084 		setContent : function(content, args) {
14085 			var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
14086 
14087 			// Setup args object
14088 			args = args || {};
14089 			args.format = args.format || 'html';
14090 			args.set = true;
14091 			args.content = content;
14092 
14093 			// Do preprocessing
14094 			if (!args.no_events)
14095 				self.onBeforeSetContent.dispatch(self, args);
14096 
14097 			content = args.content;
14098 
14099 			// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
14100 			// It will also be impossible to place the caret in the editor unless there is a BR element present
14101 			if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
14102 				forcedRootBlockName = self.settings.forced_root_block;
14103 				if (forcedRootBlockName)
14104 					content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
14105 				else
14106 					content = '<br data-mce-bogus="1">';
14107 
14108 				body.innerHTML = content;
14109 				self.selection.select(body, true);
14110 				self.selection.collapse(true);
14111 				return;
14112 			}
14113 
14114 			// Parse and serialize the html
14115 			if (args.format !== 'raw') {
14116 				content = new tinymce.html.Serializer({}, self.schema).serialize(
14117 					self.parser.parse(content)
14118 				);
14119 			}
14120 
14121 			// Set the new cleaned contents to the editor
14122 			args.content = tinymce.trim(content);
14123 			self.dom.setHTML(body, args.content);
14124 
14125 			// Do post processing
14126 			if (!args.no_events)
14127 				self.onSetContent.dispatch(self, args);
14128 
14129 			// Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise
14130 			if (!self.settings.content_editable || document.activeElement === self.getBody()) {
14131 				self.selection.normalize();
14132 			}
14133 
14134 			return args.content;
14135 		},
14136 
14137 		getContent : function(args) {
14138 			var self = this, content;
14139 
14140 			// Setup args object
14141 			args = args || {};
14142 			args.format = args.format || 'html';
14143 			args.get = true;
14144 			args.getInner = true;
14145 
14146 			// Do preprocessing
14147 			if (!args.no_events)
14148 				self.onBeforeGetContent.dispatch(self, args);
14149 
14150 			// Get raw contents or by default the cleaned contents
14151 			if (args.format == 'raw')
14152 				content = self.getBody().innerHTML;
14153 			else
14154 				content = self.serializer.serialize(self.getBody(), args);
14155 
14156 			args.content = tinymce.trim(content);
14157 
14158 			// Do post processing
14159 			if (!args.no_events)
14160 				self.onGetContent.dispatch(self, args);
14161 
14162 			return args.content;
14163 		},
14164 
14165 		isDirty : function() {
14166 			var self = this;
14167 
14168 			return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
14169 		},
14170 
14171 		getContainer : function() {
14172 			var self = this;
14173 
14174 			if (!self.container)
14175 				self.container = DOM.get(self.editorContainer || self.id + '_parent');
14176 
14177 			return self.container;
14178 		},
14179 
14180 		getContentAreaContainer : function() {
14181 			return this.contentAreaContainer;
14182 		},
14183 
14184 		getElement : function() {
14185 			return DOM.get(this.settings.content_element || this.id);
14186 		},
14187 
14188 		getWin : function() {
14189 			var self = this, elm;
14190 
14191 			if (!self.contentWindow) {
14192 				elm = DOM.get(self.id + "_ifr");
14193 
14194 				if (elm)
14195 					self.contentWindow = elm.contentWindow;
14196 			}
14197 
14198 			return self.contentWindow;
14199 		},
14200 
14201 		getDoc : function() {
14202 			var self = this, win;
14203 
14204 			if (!self.contentDocument) {
14205 				win = self.getWin();
14206 
14207 				if (win)
14208 					self.contentDocument = win.document;
14209 			}
14210 
14211 			return self.contentDocument;
14212 		},
14213 
14214 		getBody : function() {
14215 			return this.bodyElement || this.getDoc().body;
14216 		},
14217 
14218 		convertURL : function(url, name, elm) {
14219 			var self = this, settings = self.settings;
14220 
14221 			// Use callback instead
14222 			if (settings.urlconverter_callback)
14223 				return self.execCallback('urlconverter_callback', url, elm, true, name);
14224 
14225 			// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
14226 			if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0)
14227 				return url;
14228 
14229 			// Convert to relative
14230 			if (settings.relative_urls)
14231 				return self.documentBaseURI.toRelative(url);
14232 
14233 			// Convert to absolute
14234 			url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
14235 
14236 			return url;
14237 		},
14238 
14239 		addVisual : function(elm) {
14240 			var self = this, settings = self.settings, dom = self.dom, cls;
14241 
14242 			elm = elm || self.getBody();
14243 
14244 			if (!is(self.hasVisual))
14245 				self.hasVisual = settings.visual;
14246 
14247 			each(dom.select('table,a', elm), function(elm) {
14248 				var value;
14249 
14250 				switch (elm.nodeName) {
14251 					case 'TABLE':
14252 						cls = settings.visual_table_class || 'mceItemTable';
14253 						value = dom.getAttrib(elm, 'border');
14254 
14255 						if (!value || value == '0') {
14256 							if (self.hasVisual)
14257 								dom.addClass(elm, cls);
14258 							else
14259 								dom.removeClass(elm, cls);
14260 						}
14261 
14262 						return;
14263 
14264 					case 'A':
14265 						if (!dom.getAttrib(elm, 'href', false)) {
14266 							value = dom.getAttrib(elm, 'name') || elm.id;
14267 							cls = 'mceItemAnchor';
14268 
14269 							if (value) {
14270 								if (self.hasVisual)
14271 									dom.addClass(elm, cls);
14272 								else
14273 									dom.removeClass(elm, cls);
14274 							}
14275 						}
14276 
14277 						return;
14278 				}
14279 			});
14280 
14281 			self.onVisualAid.dispatch(self, elm, self.hasVisual);
14282 		},
14283 
14284 		remove : function() {
14285 			var self = this, elm = self.getContainer();
14286 
14287 			if (!self.removed) {
14288 				self.removed = 1; // Cancels post remove event execution
14289 				self.hide();
14290 
14291 				// Don't clear the window or document if content editable
14292 				// is enabled since other instances might still be present
14293 				if (!self.settings.content_editable) {
14294 					Event.unbind(self.getWin());
14295 					Event.unbind(self.getDoc());
14296 				}
14297 
14298 				Event.unbind(self.getBody());
14299 				Event.clear(elm);
14300 
14301 				self.execCallback('remove_instance_callback', self);
14302 				self.onRemove.dispatch(self);
14303 
14304 				// Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
14305 				self.onExecCommand.listeners = [];
14306 
14307 				tinymce.remove(self);
14308 				DOM.remove(elm);
14309 			}
14310 		},
14311 
14312 		destroy : function(s) {
14313 			var t = this;
14314 
14315 			// One time is enough
14316 			if (t.destroyed)
14317 				return;
14318 
14319 			// We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message
14320 			if (isGecko) {
14321 				Event.unbind(t.getDoc());
14322 				Event.unbind(t.getWin());
14323 				Event.unbind(t.getBody());
14324 			}
14325 
14326 			if (!s) {
14327 				tinymce.removeUnload(t.destroy);
14328 				tinyMCE.onBeforeUnload.remove(t._beforeUnload);
14329 
14330 				// Manual destroy
14331 				if (t.theme && t.theme.destroy)
14332 					t.theme.destroy();
14333 
14334 				// Destroy controls, selection and dom
14335 				t.controlManager.destroy();
14336 				t.selection.destroy();
14337 				t.dom.destroy();
14338 			}
14339 
14340 			if (t.formElement) {
14341 				t.formElement.submit = t.formElement._mceOldSubmit;
14342 				t.formElement._mceOldSubmit = null;
14343 			}
14344 
14345 			t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
14346 
14347 			if (t.selection)
14348 				t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
14349 
14350 			t.destroyed = 1;
14351 		},
14352 
14353 		// Internal functions
14354 
14355 		_refreshContentEditable : function() {
14356 			var self = this, body, parent;
14357 
14358 			// Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
14359 			if (self._isHidden()) {
14360 				body = self.getBody();
14361 				parent = body.parentNode;
14362 
14363 				parent.removeChild(body);
14364 				parent.appendChild(body);
14365 
14366 				body.focus();
14367 			}
14368 		},
14369 
14370 		_isHidden : function() {
14371 			var s;
14372 
14373 			if (!isGecko)
14374 				return 0;
14375 
14376 			// Weird, wheres that cursor selection?
14377 			s = this.selection.getSel();
14378 			return (!s || !s.rangeCount || s.rangeCount === 0);
14379 		}
14380 	});
14381 })(tinymce);
14382 (function(tinymce) {
14383 	var each = tinymce.each;
14384 
14385 	tinymce.Editor.prototype.setupEvents = function() {
14386 		var self = this, settings = self.settings;
14387 
14388 		// Add events to the editor
14389 		each([
14390 			'onPreInit',
14391 
14392 			'onBeforeRenderUI',
14393 
14394 			'onPostRender',
14395 
14396 			'onLoad',
14397 
14398 			'onInit',
14399 
14400 			'onRemove',
14401 
14402 			'onActivate',
14403 
14404 			'onDeactivate',
14405 
14406 			'onClick',
14407 
14408 			'onEvent',
14409 
14410 			'onMouseUp',
14411 
14412 			'onMouseDown',
14413 
14414 			'onDblClick',
14415 
14416 			'onKeyDown',
14417 
14418 			'onKeyUp',
14419 
14420 			'onKeyPress',
14421 
14422 			'onContextMenu',
14423 
14424 			'onSubmit',
14425 
14426 			'onReset',
14427 
14428 			'onPaste',
14429 
14430 			'onPreProcess',
14431 
14432 			'onPostProcess',
14433 
14434 			'onBeforeSetContent',
14435 
14436 			'onBeforeGetContent',
14437 
14438 			'onSetContent',
14439 
14440 			'onGetContent',
14441 
14442 			'onLoadContent',
14443 
14444 			'onSaveContent',
14445 
14446 			'onNodeChange',
14447 
14448 			'onChange',
14449 
14450 			'onBeforeExecCommand',
14451 
14452 			'onExecCommand',
14453 
14454 			'onUndo',
14455 
14456 			'onRedo',
14457 
14458 			'onVisualAid',
14459 
14460 			'onSetProgressState',
14461 
14462 			'onSetAttrib'
14463 		], function(name) {
14464 			self[name] = new tinymce.util.Dispatcher(self);
14465 		});
14466 
14467 		// Handle legacy cleanup_callback option
14468 		if (settings.cleanup_callback) {
14469 			self.onBeforeSetContent.add(function(ed, o) {
14470 				o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14471 			});
14472 
14473 			self.onPreProcess.add(function(ed, o) {
14474 				if (o.set)
14475 					ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
14476 
14477 				if (o.get)
14478 					ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
14479 			});
14480 
14481 			self.onPostProcess.add(function(ed, o) {
14482 				if (o.set)
14483 					o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14484 
14485 				if (o.get)						
14486 					o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
14487 			});
14488 		}
14489 
14490 		// Handle legacy save_callback option
14491 		if (settings.save_callback) {
14492 			self.onGetContent.add(function(ed, o) {
14493 				if (o.save)
14494 					o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14495 			});
14496 		}
14497 
14498 		// Handle legacy handle_event_callback option
14499 		if (settings.handle_event_callback) {
14500 			self.onEvent.add(function(ed, e, o) {
14501 				if (self.execCallback('handle_event_callback', e, ed, o) === false) {
14502 					e.preventDefault();
14503 					e.stopPropagation();
14504 				}
14505 			});
14506 		}
14507 
14508 		// Handle legacy handle_node_change_callback option
14509 		if (settings.handle_node_change_callback) {
14510 			self.onNodeChange.add(function(ed, cm, n) {
14511 				ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed());
14512 			});
14513 		}
14514 
14515 		// Handle legacy save_callback option
14516 		if (settings.save_callback) {
14517 			self.onSaveContent.add(function(ed, o) {
14518 				var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14519 
14520 				if (h)
14521 					o.content = h;
14522 			});
14523 		}
14524 
14525 		// Handle legacy onchange_callback option
14526 		if (settings.onchange_callback) {
14527 			self.onChange.add(function(ed, l) {
14528 				ed.execCallback('onchange_callback', ed, l);
14529 			});
14530 		}
14531 	};
14532 
14533 	tinymce.Editor.prototype.bindNativeEvents = function() {
14534 		// 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
14535 		var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap;
14536 
14537 		nativeToDispatcherMap = {
14538 			mouseup : 'onMouseUp',
14539 			mousedown : 'onMouseDown',
14540 			click : 'onClick',
14541 			keyup : 'onKeyUp',
14542 			keydown : 'onKeyDown',
14543 			keypress : 'onKeyPress',
14544 			submit : 'onSubmit',
14545 			reset : 'onReset',
14546 			contextmenu : 'onContextMenu',
14547 			dblclick : 'onDblClick',
14548 			paste : 'onPaste' // Doesn't work in all browsers yet
14549 		};
14550 
14551 		// Handler that takes a native event and sends it out to a dispatcher like onKeyDown
14552 		function eventHandler(evt, args) {
14553 			var type = evt.type;
14554 
14555 			// Don't fire events when it's removed
14556 			if (self.removed)
14557 				return;
14558 
14559 			// Sends the native event out to a global dispatcher then to the specific event dispatcher
14560 			if (self.onEvent.dispatch(self, evt, args) !== false) {
14561 				self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args);
14562 			}
14563 		};
14564 
14565 		// Opera doesn't support focus event for contentEditable elements so we need to fake it
14566 		function doOperaFocus(e) {
14567 			self.focus(true);
14568 		};
14569 
14570 		function nodeChanged(ed, e) {
14571 			// Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
14572 			if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {
14573 				self.selection.normalize();
14574 			}
14575 
14576 			self.nodeChanged();
14577 		}
14578 
14579 		// Add DOM events
14580 		each(nativeToDispatcherMap, function(dispatcherName, nativeName) {
14581 			var root = settings.content_editable ? self.getBody() : self.getDoc();
14582 
14583 			switch (nativeName) {
14584 				case 'contextmenu':
14585 					dom.bind(root, nativeName, eventHandler);
14586 					break;
14587 
14588 				case 'paste':
14589 					dom.bind(self.getBody(), nativeName, eventHandler);
14590 					break;
14591 
14592 				case 'submit':
14593 				case 'reset':
14594 					dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler);
14595 					break;
14596 
14597 				default:
14598 					dom.bind(root, nativeName, eventHandler);
14599 			}
14600 		});
14601 
14602 		// Set the editor as active when focused
14603 		dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) {
14604 			self.focus(true);
14605 		});
14606 
14607 		if (settings.content_editable && tinymce.isOpera) {
14608 			dom.bind(self.getBody(), 'click', doOperaFocus);
14609 			dom.bind(self.getBody(), 'keydown', doOperaFocus);
14610 		}
14611 
14612 		// Add node change handler
14613 		self.onMouseUp.add(nodeChanged);
14614 
14615 		self.onKeyUp.add(function(ed, e) {
14616 			var keyCode = e.keyCode;
14617 
14618 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)
14619 				nodeChanged(ed, e);
14620 		});
14621 
14622 		// Add reset handler
14623 		self.onReset.add(function() {
14624 			self.setContent(self.startContent, {format : 'raw'});
14625 		});
14626 
14627 		// Add shortcuts
14628 		function handleShortcut(e, execute) {
14629 			if (e.altKey || e.ctrlKey || e.metaKey) {
14630 				each(self.shortcuts, function(shortcut) {
14631 					var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey;
14632 
14633 					if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey)
14634 						return;
14635 
14636 					if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
14637 						e.preventDefault();
14638 
14639 						if (execute) {
14640 							shortcut.func.call(shortcut.scope);
14641 						}
14642 
14643 						return true;
14644 					}
14645 				});
14646 			}
14647 		};
14648 
14649 		self.onKeyUp.add(function(ed, e) {
14650 			handleShortcut(e);
14651 		});
14652 
14653 		self.onKeyPress.add(function(ed, e) {
14654 			handleShortcut(e);
14655 		});
14656 
14657 		self.onKeyDown.add(function(ed, e) {
14658 			handleShortcut(e, true);
14659 		});
14660 
14661 		if (tinymce.isOpera) {
14662 			self.onClick.add(function(ed, e) {
14663 				e.preventDefault();
14664 			});
14665 		}
14666 	};
14667 })(tinymce);
14668 (function(tinymce) {
14669 	// Added for compression purposes
14670 	var each = tinymce.each, undef, TRUE = true, FALSE = false;
14671 
14672 	tinymce.EditorCommands = function(editor) {
14673 		var dom = editor.dom,
14674 			selection = editor.selection,
14675 			commands = {state: {}, exec : {}, value : {}},
14676 			settings = editor.settings,
14677 			formatter = editor.formatter,
14678 			bookmark;
14679 
14680 		function execCommand(command, ui, value) {
14681 			var func;
14682 
14683 			command = command.toLowerCase();
14684 			if (func = commands.exec[command]) {
14685 				func(command, ui, value);
14686 				return TRUE;
14687 			}
14688 
14689 			return FALSE;
14690 		};
14691 
14692 		function queryCommandState(command) {
14693 			var func;
14694 
14695 			command = command.toLowerCase();
14696 			if (func = commands.state[command])
14697 				return func(command);
14698 
14699 			return -1;
14700 		};
14701 
14702 		function queryCommandValue(command) {
14703 			var func;
14704 
14705 			command = command.toLowerCase();
14706 			if (func = commands.value[command])
14707 				return func(command);
14708 
14709 			return FALSE;
14710 		};
14711 
14712 		function addCommands(command_list, type) {
14713 			type = type || 'exec';
14714 
14715 			each(command_list, function(callback, command) {
14716 				each(command.toLowerCase().split(','), function(command) {
14717 					commands[type][command] = callback;
14718 				});
14719 			});
14720 		};
14721 
14722 		// Expose public methods
14723 		tinymce.extend(this, {
14724 			execCommand : execCommand,
14725 			queryCommandState : queryCommandState,
14726 			queryCommandValue : queryCommandValue,
14727 			addCommands : addCommands
14728 		});
14729 
14730 		// Private methods
14731 
14732 		function execNativeCommand(command, ui, value) {
14733 			if (ui === undef)
14734 				ui = FALSE;
14735 
14736 			if (value === undef)
14737 				value = null;
14738 
14739 			return editor.getDoc().execCommand(command, ui, value);
14740 		};
14741 
14742 		function isFormatMatch(name) {
14743 			return formatter.match(name);
14744 		};
14745 
14746 		function toggleFormat(name, value) {
14747 			formatter.toggle(name, value ? {value : value} : undef);
14748 		};
14749 
14750 		function storeSelection(type) {
14751 			bookmark = selection.getBookmark(type);
14752 		};
14753 
14754 		function restoreSelection() {
14755 			selection.moveToBookmark(bookmark);
14756 		};
14757 
14758 		// Add execCommand overrides
14759 		addCommands({
14760 			// Ignore these, added for compatibility
14761 			'mceResetDesignMode,mceBeginUndoLevel' : function() {},
14762 
14763 			// Add undo manager logic
14764 			'mceEndUndoLevel,mceAddUndoLevel' : function() {
14765 				editor.undoManager.add();
14766 			},
14767 
14768 			'Cut,Copy,Paste' : function(command) {
14769 				var doc = editor.getDoc(), failed;
14770 
14771 				// Try executing the native command
14772 				try {
14773 					execNativeCommand(command);
14774 				} catch (ex) {
14775 					// Command failed
14776 					failed = TRUE;
14777 				}
14778 
14779 				// Present alert message about clipboard access not being available
14780 				if (failed || !doc.queryCommandSupported(command)) {
14781 					if (tinymce.isGecko) {
14782 						editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
14783 							if (state)
14784 								open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
14785 						});
14786 					} else
14787 						editor.windowManager.alert(editor.getLang('clipboard_no_support'));
14788 				}
14789 			},
14790 
14791 			// Override unlink command
14792 			unlink : function(command) {
14793 				if (selection.isCollapsed())
14794 					selection.select(selection.getNode());
14795 
14796 				execNativeCommand(command);
14797 				selection.collapse(FALSE);
14798 			},
14799 
14800 			// Override justify commands to use the text formatter engine
14801 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
14802 				var align = command.substring(7);
14803 
14804 				// Remove all other alignments first
14805 				each('left,center,right,full'.split(','), function(name) {
14806 					if (align != name)
14807 						formatter.remove('align' + name);
14808 				});
14809 
14810 				toggleFormat('align' + align);
14811 				execCommand('mceRepaint');
14812 			},
14813 
14814 			// Override list commands to fix WebKit bug
14815 			'InsertUnorderedList,InsertOrderedList' : function(command) {
14816 				var listElm, listParent;
14817 
14818 				execNativeCommand(command);
14819 
14820 				// WebKit produces lists within block elements so we need to split them
14821 				// we will replace the native list creation logic to custom logic later on
14822 				// TODO: Remove this when the list creation logic is removed
14823 				listElm = dom.getParent(selection.getNode(), 'ol,ul');
14824 				if (listElm) {
14825 					listParent = listElm.parentNode;
14826 
14827 					// If list is within a text block then split that block
14828 					if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
14829 						storeSelection();
14830 						dom.split(listParent, listElm);
14831 						restoreSelection();
14832 					}
14833 				}
14834 			},
14835 
14836 			// Override commands to use the text formatter engine
14837 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
14838 				toggleFormat(command);
14839 			},
14840 
14841 			// Override commands to use the text formatter engine
14842 			'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
14843 				toggleFormat(command, value);
14844 			},
14845 
14846 			FontSize : function(command, ui, value) {
14847 				var fontClasses, fontSizes;
14848 
14849 				// Convert font size 1-7 to styles
14850 				if (value >= 1 && value <= 7) {
14851 					fontSizes = tinymce.explode(settings.font_size_style_values);
14852 					fontClasses = tinymce.explode(settings.font_size_classes);
14853 
14854 					if (fontClasses)
14855 						value = fontClasses[value - 1] || value;
14856 					else
14857 						value = fontSizes[value - 1] || value;
14858 				}
14859 
14860 				toggleFormat(command, value);
14861 			},
14862 
14863 			RemoveFormat : function(command) {
14864 				formatter.remove(command);
14865 			},
14866 
14867 			mceBlockQuote : function(command) {
14868 				toggleFormat('blockquote');
14869 			},
14870 
14871 			FormatBlock : function(command, ui, value) {
14872 				return toggleFormat(value || 'p');
14873 			},
14874 
14875 			mceCleanup : function() {
14876 				var bookmark = selection.getBookmark();
14877 
14878 				editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
14879 
14880 				selection.moveToBookmark(bookmark);
14881 			},
14882 
14883 			mceRemoveNode : function(command, ui, value) {
14884 				var node = value || selection.getNode();
14885 
14886 				// Make sure that the body node isn't removed
14887 				if (node != editor.getBody()) {
14888 					storeSelection();
14889 					editor.dom.remove(node, TRUE);
14890 					restoreSelection();
14891 				}
14892 			},
14893 
14894 			mceSelectNodeDepth : function(command, ui, value) {
14895 				var counter = 0;
14896 
14897 				dom.getParent(selection.getNode(), function(node) {
14898 					if (node.nodeType == 1 && counter++ == value) {
14899 						selection.select(node);
14900 						return FALSE;
14901 					}
14902 				}, editor.getBody());
14903 			},
14904 
14905 			mceSelectNode : function(command, ui, value) {
14906 				selection.select(value);
14907 			},
14908 
14909 			mceInsertContent : function(command, ui, value) {
14910 				var parser, serializer, parentNode, rootNode, fragment, args,
14911 					marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
14912 
14913 				//selection.normalize();
14914 
14915 				// Setup parser and serializer
14916 				parser = editor.parser;
14917 				serializer = new tinymce.html.Serializer({}, editor.schema);
14918 				bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
14919 
14920 				// Run beforeSetContent handlers on the HTML to be inserted
14921 				args = {content: value, format: 'html'};
14922 				selection.onBeforeSetContent.dispatch(selection, args);
14923 				value = args.content;
14924 
14925 				// Add caret at end of contents if it's missing
14926 				if (value.indexOf('{$caret}') == -1)
14927 					value += '{$caret}';
14928 
14929 				// Replace the caret marker with a span bookmark element
14930 				value = value.replace(/\{\$caret\}/, bookmarkHtml);
14931 
14932 				// Insert node maker where we will insert the new HTML and get it's parent
14933 				if (!selection.isCollapsed())
14934 					editor.getDoc().execCommand('Delete', false, null);
14935 
14936 				parentNode = selection.getNode();
14937 
14938 				// Parse the fragment within the context of the parent node
14939 				args = {context : parentNode.nodeName.toLowerCase()};
14940 				fragment = parser.parse(value, args);
14941 
14942 				// Move the caret to a more suitable location
14943 				node = fragment.lastChild;
14944 				if (node.attr('id') == 'mce_marker') {
14945 					marker = node;
14946 
14947 					for (node = node.prev; node; node = node.walk(true)) {
14948 						if (node.type == 3 || !dom.isBlock(node.name)) {
14949 							node.parent.insert(marker, node, node.name === 'br');
14950 							break;
14951 						}
14952 					}
14953 				}
14954 
14955 				// If parser says valid we can insert the contents into that parent
14956 				if (!args.invalid) {
14957 					value = serializer.serialize(fragment);
14958 
14959 					// Check if parent is empty or only has one BR element then set the innerHTML of that parent
14960 					node = parentNode.firstChild;
14961 					node2 = parentNode.lastChild;
14962 					if (!node || (node === node2 && node.nodeName === 'BR'))
14963 						dom.setHTML(parentNode, value);
14964 					else
14965 						selection.setContent(value);
14966 				} else {
14967 					// If the fragment was invalid within that context then we need
14968 					// to parse and process the parent it's inserted into
14969 
14970 					// Insert bookmark node and get the parent
14971 					selection.setContent(bookmarkHtml);
14972 					parentNode = editor.selection.getNode();
14973 					rootNode = editor.getBody();
14974 
14975 					// Opera will return the document node when selection is in root
14976 					if (parentNode.nodeType == 9)
14977 						parentNode = node = rootNode;
14978 					else
14979 						node = parentNode;
14980 
14981 					// Find the ancestor just before the root element
14982 					while (node !== rootNode) {
14983 						parentNode = node;
14984 						node = node.parentNode;
14985 					}
14986 
14987 					// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
14988 					value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
14989 					value = serializer.serialize(
14990 						parser.parse(
14991 							// Need to replace by using a function since $ in the contents would otherwise be a problem
14992 							value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
14993 								return serializer.serialize(fragment);
14994 							})
14995 						)
14996 					);
14997 
14998 					// Set the inner/outer HTML depending on if we are in the root or not
14999 					if (parentNode == rootNode)
15000 						dom.setHTML(rootNode, value);
15001 					else
15002 						dom.setOuterHTML(parentNode, value);
15003 				}
15004 
15005 				marker = dom.get('mce_marker');
15006 
15007 				// Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
15008 				nodeRect = dom.getRect(marker);
15009 				viewPortRect = dom.getViewPort(editor.getWin());
15010 
15011 				// Check if node is out side the viewport if it is then scroll to it
15012 				if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
15013 					(nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
15014 					viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
15015 					viewportBodyElement.scrollLeft = nodeRect.x;
15016 					viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
15017 				}
15018 
15019 				// Move selection before marker and remove it
15020 				rng = dom.createRng();
15021 
15022 				// If previous sibling is a text node set the selection to the end of that node
15023 				node = marker.previousSibling;
15024 				if (node && node.nodeType == 3) {
15025 					rng.setStart(node, node.nodeValue.length);
15026 				} else {
15027 					// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
15028 					rng.setStartBefore(marker);
15029 					rng.setEndBefore(marker);
15030 				}
15031 
15032 				// Remove the marker node and set the new range
15033 				dom.remove(marker);
15034 				selection.setRng(rng);
15035 
15036 				// Dispatch after event and add any visual elements needed
15037 				selection.onSetContent.dispatch(selection, args);
15038 				editor.addVisual();
15039 			},
15040 
15041 			mceInsertRawHTML : function(command, ui, value) {
15042 				selection.setContent('tiny_mce_marker');
15043 				editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
15044 			},
15045 
15046 			mceToggleFormat : function(command, ui, value) {
15047 				toggleFormat(value);
15048 			},
15049 
15050 			mceSetContent : function(command, ui, value) {
15051 				editor.setContent(value);
15052 			},
15053 
15054 			'Indent,Outdent' : function(command) {
15055 				var intentValue, indentUnit, value;
15056 
15057 				// Setup indent level
15058 				intentValue = settings.indentation;
15059 				indentUnit = /[a-z%]+$/i.exec(intentValue);
15060 				intentValue = parseInt(intentValue);
15061 
15062 				if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
15063 					// If forced_root_blocks is set to false we don't have a block to indent so lets create a div
15064 					if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
15065 						formatter.apply('div');
15066 					}
15067 
15068 					each(selection.getSelectedBlocks(), function(element) {
15069 						if (command == 'outdent') {
15070 							value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
15071 							dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
15072 						} else
15073 							dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
15074 					});
15075 				} else
15076 					execNativeCommand(command);
15077 			},
15078 
15079 			mceRepaint : function() {
15080 				var bookmark;
15081 
15082 				if (tinymce.isGecko) {
15083 					try {
15084 						storeSelection(TRUE);
15085 
15086 						if (selection.getSel())
15087 							selection.getSel().selectAllChildren(editor.getBody());
15088 
15089 						selection.collapse(TRUE);
15090 						restoreSelection();
15091 					} catch (ex) {
15092 						// Ignore
15093 					}
15094 				}
15095 			},
15096 
15097 			mceToggleFormat : function(command, ui, value) {
15098 				formatter.toggle(value);
15099 			},
15100 
15101 			InsertHorizontalRule : function() {
15102 				editor.execCommand('mceInsertContent', false, '<hr />');
15103 			},
15104 
15105 			mceToggleVisualAid : function() {
15106 				editor.hasVisual = !editor.hasVisual;
15107 				editor.addVisual();
15108 			},
15109 
15110 			mceReplaceContent : function(command, ui, value) {
15111 				editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
15112 			},
15113 
15114 			mceInsertLink : function(command, ui, value) {
15115 				var anchor;
15116 
15117 				if (typeof(value) == 'string')
15118 					value = {href : value};
15119 
15120 				anchor = dom.getParent(selection.getNode(), 'a');
15121 
15122 				// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
15123 				value.href = value.href.replace(' ', '%20');
15124 
15125 				// Remove existing links if there could be child links or that the href isn't specified
15126 				if (!anchor || !value.href) {
15127 					formatter.remove('link');
15128 				}		
15129 
15130 				// Apply new link to selection
15131 				if (value.href) {
15132 					formatter.apply('link', value, anchor);
15133 				}
15134 			},
15135 
15136 			selectAll : function() {
15137 				var root = dom.getRoot(), rng = dom.createRng();
15138 
15139 				rng.setStart(root, 0);
15140 				rng.setEnd(root, root.childNodes.length);
15141 
15142 				editor.selection.setRng(rng);
15143 			}
15144 		});
15145 
15146 		// Add queryCommandState overrides
15147 		addCommands({
15148 			// Override justify commands
15149 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
15150 				var name = 'align' + command.substring(7);
15151 				var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
15152 				var matches = tinymce.map(nodes, function(node) {
15153 					return !!formatter.matchNode(node, name);
15154 				});
15155 				return tinymce.inArray(matches, TRUE) !== -1;
15156 			},
15157 
15158 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
15159 				return isFormatMatch(command);
15160 			},
15161 
15162 			mceBlockQuote : function() {
15163 				return isFormatMatch('blockquote');
15164 			},
15165 
15166 			Outdent : function() {
15167 				var node;
15168 
15169 				if (settings.inline_styles) {
15170 					if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15171 						return TRUE;
15172 
15173 					if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15174 						return TRUE;
15175 				}
15176 
15177 				return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
15178 			},
15179 
15180 			'InsertUnorderedList,InsertOrderedList' : function(command) {
15181 				return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
15182 			}
15183 		}, 'state');
15184 
15185 		// Add queryCommandValue overrides
15186 		addCommands({
15187 			'FontSize,FontName' : function(command) {
15188 				var value = 0, parent;
15189 
15190 				if (parent = dom.getParent(selection.getNode(), 'span')) {
15191 					if (command == 'fontsize')
15192 						value = parent.style.fontSize;
15193 					else
15194 						value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
15195 				}
15196 
15197 				return value;
15198 			}
15199 		}, 'value');
15200 
15201 		// Add undo manager logic
15202 		addCommands({
15203 			Undo : function() {
15204 				editor.undoManager.undo();
15205 			},
15206 
15207 			Redo : function() {
15208 				editor.undoManager.redo();
15209 			}
15210 		});
15211 	};
15212 })(tinymce);
15213 
15214 (function(tinymce) {
15215 	var Dispatcher = tinymce.util.Dispatcher;
15216 
15217 	tinymce.UndoManager = function(editor) {
15218 		var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo;
15219 
15220 		function getContent() {
15221 			// Remove whitespace before/after and remove pure bogus nodes
15222 			return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, ''));
15223 		};
15224 
15225 		function addNonTypingUndoLevel() {
15226 			self.typing = false;
15227 			self.add();
15228 		};
15229 
15230 		// Create event instances
15231 		onBeforeAdd = new Dispatcher(self);
15232 		onAdd       = new Dispatcher(self);
15233 		onUndo      = new Dispatcher(self);
15234 		onRedo      = new Dispatcher(self);
15235 
15236 		// Pass though onAdd event from UndoManager to Editor as onChange
15237 		onAdd.add(function(undoman, level) {
15238 			if (undoman.hasUndo())
15239 				return editor.onChange.dispatch(editor, level, undoman);
15240 		});
15241 
15242 		// Pass though onUndo event from UndoManager to Editor
15243 		onUndo.add(function(undoman, level) {
15244 			return editor.onUndo.dispatch(editor, level, undoman);
15245 		});
15246 
15247 		// Pass though onRedo event from UndoManager to Editor
15248 		onRedo.add(function(undoman, level) {
15249 			return editor.onRedo.dispatch(editor, level, undoman);
15250 		});
15251 
15252 		// Add initial undo level when the editor is initialized
15253 		editor.onInit.add(function() {
15254 			self.add();
15255 		});
15256 
15257 		// Get position before an execCommand is processed
15258 		editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) {
15259 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15260 				self.beforeChange();
15261 			}
15262 		});
15263 
15264 		// Add undo level after an execCommand call was made
15265 		editor.onExecCommand.add(function(ed, cmd, ui, val, args) {
15266 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15267 				self.add();
15268 			}
15269 		});
15270 
15271 		// Add undo level on save contents, drag end and blur/focusout
15272 		editor.onSaveContent.add(addNonTypingUndoLevel);
15273 		editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
15274 		editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) {
15275 			if (!editor.removed && self.typing) {
15276 				addNonTypingUndoLevel();
15277 			}
15278 		});
15279 
15280 		editor.onKeyUp.add(function(editor, e) {
15281 			var keyCode = e.keyCode;
15282 
15283 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
15284 				addNonTypingUndoLevel();
15285 			}
15286 		});
15287 
15288 		editor.onKeyDown.add(function(editor, e) {
15289 			var keyCode = e.keyCode;
15290 
15291 			// Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
15292 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
15293 				if (self.typing) {
15294 					addNonTypingUndoLevel();
15295 				}
15296 
15297 				return;
15298 			}
15299 
15300 			// If key isn't shift,ctrl,alt,capslock,metakey
15301 			if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
15302 				self.beforeChange();
15303 				self.typing = true;
15304 				self.add();
15305 			}
15306 		});
15307 
15308 		editor.onMouseDown.add(function(editor, e) {
15309 			if (self.typing) {
15310 				addNonTypingUndoLevel();
15311 			}
15312 		});
15313 
15314 		// Add keyboard shortcuts for undo/redo keys
15315 		editor.addShortcut('ctrl+z', 'undo_desc', 'Undo');
15316 		editor.addShortcut('ctrl+y', 'redo_desc', 'Redo');
15317 
15318 		self = {
15319 			// Explose for debugging reasons
15320 			data : data,
15321 
15322 			typing : false,
15323 			
15324 			onBeforeAdd: onBeforeAdd,
15325 
15326 			onAdd : onAdd,
15327 
15328 			onUndo : onUndo,
15329 
15330 			onRedo : onRedo,
15331 
15332 			beforeChange : function() {
15333 				beforeBookmark = editor.selection.getBookmark(2, true);
15334 			},
15335 
15336 			add : function(level) {
15337 				var i, settings = editor.settings, lastLevel;
15338 
15339 				level = level || {};
15340 				level.content = getContent();
15341 				
15342 				self.onBeforeAdd.dispatch(self, level);
15343 
15344 				// Add undo level if needed
15345 				lastLevel = data[index];
15346 				if (lastLevel && lastLevel.content == level.content)
15347 					return null;
15348 
15349 				// Set before bookmark on previous level
15350 				if (data[index])
15351 					data[index].beforeBookmark = beforeBookmark;
15352 
15353 				// Time to compress
15354 				if (settings.custom_undo_redo_levels) {
15355 					if (data.length > settings.custom_undo_redo_levels) {
15356 						for (i = 0; i < data.length - 1; i++)
15357 							data[i] = data[i + 1];
15358 
15359 						data.length--;
15360 						index = data.length;
15361 					}
15362 				}
15363 
15364 				// Get a non intrusive normalized bookmark
15365 				level.bookmark = editor.selection.getBookmark(2, true);
15366 
15367 				// Crop array if needed
15368 				if (index < data.length - 1)
15369 					data.length = index + 1;
15370 
15371 				data.push(level);
15372 				index = data.length - 1;
15373 
15374 				self.onAdd.dispatch(self, level);
15375 				editor.isNotDirty = 0;
15376 
15377 				return level;
15378 			},
15379 
15380 			undo : function() {
15381 				var level, i;
15382 
15383 				if (self.typing) {
15384 					self.add();
15385 					self.typing = false;
15386 				}
15387 
15388 				if (index > 0) {
15389 					level = data[--index];
15390 
15391 					editor.setContent(level.content, {format : 'raw'});
15392 					editor.selection.moveToBookmark(level.beforeBookmark);
15393 
15394 					self.onUndo.dispatch(self, level);
15395 				}
15396 
15397 				return level;
15398 			},
15399 
15400 			redo : function() {
15401 				var level;
15402 
15403 				if (index < data.length - 1) {
15404 					level = data[++index];
15405 
15406 					editor.setContent(level.content, {format : 'raw'});
15407 					editor.selection.moveToBookmark(level.bookmark);
15408 
15409 					self.onRedo.dispatch(self, level);
15410 				}
15411 
15412 				return level;
15413 			},
15414 
15415 			clear : function() {
15416 				data = [];
15417 				index = 0;
15418 				self.typing = false;
15419 			},
15420 
15421 			hasUndo : function() {
15422 				return index > 0 || this.typing;
15423 			},
15424 
15425 			hasRedo : function() {
15426 				return index < data.length - 1 && !this.typing;
15427 			}
15428 		};
15429 
15430 		return self;
15431 	};
15432 })(tinymce);
15433 
15434 tinymce.ForceBlocks = function(editor) {
15435 	var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements();
15436 
15437 	function addRootBlocks() {
15438 		var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument;
15439 
15440 		if (!node || node.nodeType !== 1 || !settings.forced_root_block)
15441 			return;
15442 
15443 		// Check if node is wrapped in block
15444 		while (node && node != rootNode) {
15445 			if (blockElements[node.nodeName])
15446 				return;
15447 
15448 			node = node.parentNode;
15449 		}
15450 
15451 		// Get current selection
15452 		rng = selection.getRng();
15453 		if (rng.setStart) {
15454 			startContainer = rng.startContainer;
15455 			startOffset = rng.startOffset;
15456 			endContainer = rng.endContainer;
15457 			endOffset = rng.endOffset;
15458 		} else {
15459 			// Force control range into text range
15460 			if (rng.item) {
15461 				node = rng.item(0);
15462 				rng = editor.getDoc().body.createTextRange();
15463 				rng.moveToElementText(node);
15464 			}
15465 
15466 			isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc();
15467 			tmpRng = rng.duplicate();
15468 			tmpRng.collapse(true);
15469 			startOffset = tmpRng.move('character', offset) * -1;
15470 
15471 			if (!tmpRng.collapsed) {
15472 				tmpRng = rng.duplicate();
15473 				tmpRng.collapse(false);
15474 				endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
15475 			}
15476 		}
15477 
15478 		// Wrap non block elements and text nodes
15479 		node = rootNode.firstChild;
15480 		while (node) {
15481 			if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
15482 				if (!rootBlockNode) {
15483 					rootBlockNode = dom.create(settings.forced_root_block);
15484 					node.parentNode.insertBefore(rootBlockNode, node);
15485 					wrapped = true;
15486 				}
15487 
15488 				tempNode = node;
15489 				node = node.nextSibling;
15490 				rootBlockNode.appendChild(tempNode);
15491 			} else {
15492 				rootBlockNode = null;
15493 				node = node.nextSibling;
15494 			}
15495 		}
15496 
15497 		if (wrapped) {
15498 			if (rng.setStart) {
15499 				rng.setStart(startContainer, startOffset);
15500 				rng.setEnd(endContainer, endOffset);
15501 				selection.setRng(rng);
15502 			} else {
15503 				// Only select if the previous selection was inside the document to prevent auto focus in quirks mode
15504 				if (isInEditorDocument) {
15505 					try {
15506 						rng = editor.getDoc().body.createTextRange();
15507 						rng.moveToElementText(rootNode);
15508 						rng.collapse(true);
15509 						rng.moveStart('character', startOffset);
15510 
15511 						if (endOffset > 0)
15512 							rng.moveEnd('character', endOffset);
15513 
15514 						rng.select();
15515 					} catch (ex) {
15516 						// Ignore
15517 					}
15518 				}
15519 			}
15520 
15521 			editor.nodeChanged();
15522 		}
15523 	};
15524 
15525 	// Force root blocks
15526 	if (settings.forced_root_block) {
15527 		editor.onKeyUp.add(addRootBlocks);
15528 		editor.onNodeChange.add(addRootBlocks);
15529 	}
15530 };
15531 
15532 (function(tinymce) {
15533 	// Shorten names
15534 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
15535 
15536 	tinymce.create('tinymce.ControlManager', {
15537 		ControlManager : function(ed, s) {
15538 			var t = this, i;
15539 
15540 			s = s || {};
15541 			t.editor = ed;
15542 			t.controls = {};
15543 			t.onAdd = new tinymce.util.Dispatcher(t);
15544 			t.onPostRender = new tinymce.util.Dispatcher(t);
15545 			t.prefix = s.prefix || ed.id + '_';
15546 			t._cls = {};
15547 
15548 			t.onPostRender.add(function() {
15549 				each(t.controls, function(c) {
15550 					c.postRender();
15551 				});
15552 			});
15553 		},
15554 
15555 		get : function(id) {
15556 			return this.controls[this.prefix + id] || this.controls[id];
15557 		},
15558 
15559 		setActive : function(id, s) {
15560 			var c = null;
15561 
15562 			if (c = this.get(id))
15563 				c.setActive(s);
15564 
15565 			return c;
15566 		},
15567 
15568 		setDisabled : function(id, s) {
15569 			var c = null;
15570 
15571 			if (c = this.get(id))
15572 				c.setDisabled(s);
15573 
15574 			return c;
15575 		},
15576 
15577 		add : function(c) {
15578 			var t = this;
15579 
15580 			if (c) {
15581 				t.controls[c.id] = c;
15582 				t.onAdd.dispatch(c, t);
15583 			}
15584 
15585 			return c;
15586 		},
15587 
15588 		createControl : function(name) {
15589 			var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName;
15590 
15591 			// Build control factory cache
15592 			if (!self.controlFactories) {
15593 				self.controlFactories = [];
15594 				each(editor.plugins, function(plugin) {
15595 					if (plugin.createControl) {
15596 						self.controlFactories.push(plugin);
15597 					}
15598 				});
15599 			}
15600 
15601 			// Create controls by asking cached factories
15602 			factories = self.controlFactories;
15603 			for (i = 0, l = factories.length; i < l; i++) {
15604 				ctrl = factories[i].createControl(name, self);
15605 
15606 				if (ctrl) {
15607 					return self.add(ctrl);
15608 				}
15609 			}
15610 
15611 			// Create sepearator
15612 			if (name === "|" || name === "separator") {
15613 				return self.createSeparator();
15614 			}
15615 
15616 			// Create control from button collection
15617 			if (editor.buttons && (ctrl = editor.buttons[name])) {
15618 				return self.createButton(name, ctrl);
15619 			}
15620 
15621 			return self.add(ctrl);
15622 		},
15623 
15624 		createDropMenu : function(id, s, cc) {
15625 			var t = this, ed = t.editor, c, bm, v, cls;
15626 
15627 			s = extend({
15628 				'class' : 'mceDropDown',
15629 				constrain : ed.settings.constrain_menus
15630 			}, s);
15631 
15632 			s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
15633 			if (v = ed.getParam('skin_variant'))
15634 				s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
15635 
15636 			s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : '';
15637 
15638 			id = t.prefix + id;
15639 			cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
15640 			c = t.controls[id] = new cls(id, s);
15641 			c.onAddItem.add(function(c, o) {
15642 				var s = o.settings;
15643 
15644 				s.title = ed.getLang(s.title, s.title);
15645 
15646 				if (!s.onclick) {
15647 					s.onclick = function(v) {
15648 						if (s.cmd)
15649 							ed.execCommand(s.cmd, s.ui || false, s.value);
15650 					};
15651 				}
15652 			});
15653 
15654 			ed.onRemove.add(function() {
15655 				c.destroy();
15656 			});
15657 
15658 			// Fix for bug #1897785, #1898007
15659 			if (tinymce.isIE) {
15660 				c.onShowMenu.add(function() {
15661 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
15662 					ed.focus();
15663 
15664 					bm = ed.selection.getBookmark(1);
15665 				});
15666 
15667 				c.onHideMenu.add(function() {
15668 					if (bm) {
15669 						ed.selection.moveToBookmark(bm);
15670 						bm = 0;
15671 					}
15672 				});
15673 			}
15674 
15675 			return t.add(c);
15676 		},
15677 
15678 		createListBox : function(id, s, cc) {
15679 			var t = this, ed = t.editor, cmd, c, cls;
15680 
15681 			if (t.get(id))
15682 				return null;
15683 
15684 			s.title = ed.translate(s.title);
15685 			s.scope = s.scope || ed;
15686 
15687 			if (!s.onselect) {
15688 				s.onselect = function(v) {
15689 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15690 				};
15691 			}
15692 
15693 			s = extend({
15694 				title : s.title,
15695 				'class' : 'mce_' + id,
15696 				scope : s.scope,
15697 				control_manager : t
15698 			}, s);
15699 
15700 			id = t.prefix + id;
15701 
15702 
15703 			function useNativeListForAccessibility(ed) {
15704 				return ed.settings.use_accessible_selects && !tinymce.isGecko
15705 			}
15706 
15707 			if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))
15708 				c = new tinymce.ui.NativeListBox(id, s);
15709 			else {
15710 				cls = cc || t._cls.listbox || tinymce.ui.ListBox;
15711 				c = new cls(id, s, ed);
15712 			}
15713 
15714 			t.controls[id] = c;
15715 
15716 			// Fix focus problem in Safari
15717 			if (tinymce.isWebKit) {
15718 				c.onPostRender.add(function(c, n) {
15719 					// Store bookmark on mousedown
15720 					Event.add(n, 'mousedown', function() {
15721 						ed.bookmark = ed.selection.getBookmark(1);
15722 					});
15723 
15724 					// Restore on focus, since it might be lost
15725 					Event.add(n, 'focus', function() {
15726 						ed.selection.moveToBookmark(ed.bookmark);
15727 						ed.bookmark = null;
15728 					});
15729 				});
15730 			}
15731 
15732 			if (c.hideMenu)
15733 				ed.onMouseDown.add(c.hideMenu, c);
15734 
15735 			return t.add(c);
15736 		},
15737 
15738 		createButton : function(id, s, cc) {
15739 			var t = this, ed = t.editor, o, c, cls;
15740 
15741 			if (t.get(id))
15742 				return null;
15743 
15744 			s.title = ed.translate(s.title);
15745 			s.label = ed.translate(s.label);
15746 			s.scope = s.scope || ed;
15747 
15748 			if (!s.onclick && !s.menu_button) {
15749 				s.onclick = function() {
15750 					ed.execCommand(s.cmd, s.ui || false, s.value);
15751 				};
15752 			}
15753 
15754 			s = extend({
15755 				title : s.title,
15756 				'class' : 'mce_' + id,
15757 				unavailable_prefix : ed.getLang('unavailable', ''),
15758 				scope : s.scope,
15759 				control_manager : t
15760 			}, s);
15761 
15762 			id = t.prefix + id;
15763 
15764 			if (s.menu_button) {
15765 				cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
15766 				c = new cls(id, s, ed);
15767 				ed.onMouseDown.add(c.hideMenu, c);
15768 			} else {
15769 				cls = t._cls.button || tinymce.ui.Button;
15770 				c = new cls(id, s, ed);
15771 			}
15772 
15773 			return t.add(c);
15774 		},
15775 
15776 		createMenuButton : function(id, s, cc) {
15777 			s = s || {};
15778 			s.menu_button = 1;
15779 
15780 			return this.createButton(id, s, cc);
15781 		},
15782 
15783 		createSplitButton : function(id, s, cc) {
15784 			var t = this, ed = t.editor, cmd, c, cls;
15785 
15786 			if (t.get(id))
15787 				return null;
15788 
15789 			s.title = ed.translate(s.title);
15790 			s.scope = s.scope || ed;
15791 
15792 			if (!s.onclick) {
15793 				s.onclick = function(v) {
15794 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15795 				};
15796 			}
15797 
15798 			if (!s.onselect) {
15799 				s.onselect = function(v) {
15800 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15801 				};
15802 			}
15803 
15804 			s = extend({
15805 				title : s.title,
15806 				'class' : 'mce_' + id,
15807 				scope : s.scope,
15808 				control_manager : t
15809 			}, s);
15810 
15811 			id = t.prefix + id;
15812 			cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
15813 			c = t.add(new cls(id, s, ed));
15814 			ed.onMouseDown.add(c.hideMenu, c);
15815 
15816 			return c;
15817 		},
15818 
15819 		createColorSplitButton : function(id, s, cc) {
15820 			var t = this, ed = t.editor, cmd, c, cls, bm;
15821 
15822 			if (t.get(id))
15823 				return null;
15824 
15825 			s.title = ed.translate(s.title);
15826 			s.scope = s.scope || ed;
15827 
15828 			if (!s.onclick) {
15829 				s.onclick = function(v) {
15830 					if (tinymce.isIE)
15831 						bm = ed.selection.getBookmark(1);
15832 
15833 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15834 				};
15835 			}
15836 
15837 			if (!s.onselect) {
15838 				s.onselect = function(v) {
15839 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15840 				};
15841 			}
15842 
15843 			s = extend({
15844 				title : s.title,
15845 				'class' : 'mce_' + id,
15846 				'menu_class' : ed.getParam('skin') + 'Skin',
15847 				scope : s.scope,
15848 				more_colors_title : ed.getLang('more_colors')
15849 			}, s);
15850 
15851 			id = t.prefix + id;
15852 			cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
15853 			c = new cls(id, s, ed);
15854 			ed.onMouseDown.add(c.hideMenu, c);
15855 
15856 			// Remove the menu element when the editor is removed
15857 			ed.onRemove.add(function() {
15858 				c.destroy();
15859 			});
15860 
15861 			// Fix for bug #1897785, #1898007
15862 			if (tinymce.isIE) {
15863 				c.onShowMenu.add(function() {
15864 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
15865 					ed.focus();
15866 					bm = ed.selection.getBookmark(1);
15867 				});
15868 
15869 				c.onHideMenu.add(function() {
15870 					if (bm) {
15871 						ed.selection.moveToBookmark(bm);
15872 						bm = 0;
15873 					}
15874 				});
15875 			}
15876 
15877 			return t.add(c);
15878 		},
15879 
15880 		createToolbar : function(id, s, cc) {
15881 			var c, t = this, cls;
15882 
15883 			id = t.prefix + id;
15884 			cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
15885 			c = new cls(id, s, t.editor);
15886 
15887 			if (t.get(id))
15888 				return null;
15889 
15890 			return t.add(c);
15891 		},
15892 		
15893 		createToolbarGroup : function(id, s, cc) {
15894 			var c, t = this, cls;
15895 			id = t.prefix + id;
15896 			cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
15897 			c = new cls(id, s, t.editor);
15898 			
15899 			if (t.get(id))
15900 				return null;
15901 			
15902 			return t.add(c);
15903 		},
15904 
15905 		createSeparator : function(cc) {
15906 			var cls = cc || this._cls.separator || tinymce.ui.Separator;
15907 
15908 			return new cls();
15909 		},
15910 
15911 		setControlType : function(n, c) {
15912 			return this._cls[n.toLowerCase()] = c;
15913 		},
15914 	
15915 		destroy : function() {
15916 			each(this.controls, function(c) {
15917 				c.destroy();
15918 			});
15919 
15920 			this.controls = null;
15921 		}
15922 	});
15923 })(tinymce);
15924 
15925 (function(tinymce) {
15926 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
15927 
15928 	tinymce.create('tinymce.WindowManager', {
15929 		WindowManager : function(ed) {
15930 			var t = this;
15931 
15932 			t.editor = ed;
15933 			t.onOpen = new Dispatcher(t);
15934 			t.onClose = new Dispatcher(t);
15935 			t.params = {};
15936 			t.features = {};
15937 		},
15938 
15939 		open : function(s, p) {
15940 			var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
15941 
15942 			// Default some options
15943 			s = s || {};
15944 			p = p || {};
15945 			sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
15946 			sh = isOpera ? vp.h : screen.height;
15947 			s.name = s.name || 'mc_' + new Date().getTime();
15948 			s.width = parseInt(s.width || 320);
15949 			s.height = parseInt(s.height || 240);
15950 			s.resizable = true;
15951 			s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
15952 			s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
15953 			p.inline = false;
15954 			p.mce_width = s.width;
15955 			p.mce_height = s.height;
15956 			p.mce_auto_focus = s.auto_focus;
15957 
15958 			if (mo) {
15959 				if (isIE) {
15960 					s.center = true;
15961 					s.help = false;
15962 					s.dialogWidth = s.width + 'px';
15963 					s.dialogHeight = s.height + 'px';
15964 					s.scroll = s.scrollbars || false;
15965 				}
15966 			}
15967 
15968 			// Build features string
15969 			each(s, function(v, k) {
15970 				if (tinymce.is(v, 'boolean'))
15971 					v = v ? 'yes' : 'no';
15972 
15973 				if (!/^(name|url)$/.test(k)) {
15974 					if (isIE && mo)
15975 						f += (f ? ';' : '') + k + ':' + v;
15976 					else
15977 						f += (f ? ',' : '') + k + '=' + v;
15978 				}
15979 			});
15980 
15981 			t.features = s;
15982 			t.params = p;
15983 			t.onOpen.dispatch(t, s, p);
15984 
15985 			u = s.url || s.file;
15986 			u = tinymce._addVer(u);
15987 
15988 			try {
15989 				if (isIE && mo) {
15990 					w = 1;
15991 					window.showModalDialog(u, window, f);
15992 				} else
15993 					w = window.open(u, s.name, f);
15994 			} catch (ex) {
15995 				// Ignore
15996 			}
15997 
15998 			if (!w)
15999 				alert(t.editor.getLang('popup_blocked'));
16000 		},
16001 
16002 		close : function(w) {
16003 			w.close();
16004 			this.onClose.dispatch(this);
16005 		},
16006 
16007 		createInstance : function(cl, a, b, c, d, e) {
16008 			var f = tinymce.resolve(cl);
16009 
16010 			return new f(a, b, c, d, e);
16011 		},
16012 
16013 		confirm : function(t, cb, s, w) {
16014 			w = w || window;
16015 
16016 			cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
16017 		},
16018 
16019 		alert : function(tx, cb, s, w) {
16020 			var t = this;
16021 
16022 			w = w || window;
16023 			w.alert(t._decode(t.editor.getLang(tx, tx)));
16024 
16025 			if (cb)
16026 				cb.call(s || t);
16027 		},
16028 
16029 		resizeBy : function(dw, dh, win) {
16030 			win.resizeBy(dw, dh);
16031 		},
16032 
16033 		// Internal functions
16034 
16035 		_decode : function(s) {
16036 			return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
16037 		}
16038 	});
16039 }(tinymce));
16040 (function(tinymce) {
16041 	tinymce.Formatter = function(ed) {
16042 		var formats = {},
16043 			each = tinymce.each,
16044 			dom = ed.dom,
16045 			selection = ed.selection,
16046 			TreeWalker = tinymce.dom.TreeWalker,
16047 			rangeUtils = new tinymce.dom.RangeUtils(dom),
16048 			isValid = ed.schema.isValidChild,
16049 			isBlock = dom.isBlock,
16050 			forcedRootBlock = ed.settings.forced_root_block,
16051 			nodeIndex = dom.nodeIndex,
16052 			INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF',
16053 			MCE_ATTR_RE = /^(src|href|style)$/,
16054 			FALSE = false,
16055 			TRUE = true,
16056 			formatChangeData,
16057 			undef,
16058 			getContentEditable = dom.getContentEditable;
16059 
16060 		function isArray(obj) {
16061 			return obj instanceof Array;
16062 		};
16063 
16064 		function getParents(node, selector) {
16065 			return dom.getParents(node, selector, dom.getRoot());
16066 		};
16067 
16068 		function isCaretNode(node) {
16069 			return node.nodeType === 1 && node.id === '_mce_caret';
16070 		};
16071 
16072 		function defaultFormats() {
16073 			register({
16074 				alignleft : [
16075 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'},
16076 					{selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
16077 				],
16078 
16079 				aligncenter : [
16080 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'},
16081 					{selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
16082 					{selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
16083 				],
16084 
16085 				alignright : [
16086 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'},
16087 					{selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
16088 				],
16089 
16090 				alignfull : [
16091 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'}
16092 				],
16093 
16094 				bold : [
16095 					{inline : 'strong', remove : 'all'},
16096 					{inline : 'span', styles : {fontWeight : 'bold'}},
16097 					{inline : 'b', remove : 'all'}
16098 				],
16099 
16100 				italic : [
16101 					{inline : 'em', remove : 'all'},
16102 					{inline : 'span', styles : {fontStyle : 'italic'}},
16103 					{inline : 'i', remove : 'all'}
16104 				],
16105 
16106 				underline : [
16107 					{inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
16108 					{inline : 'u', remove : 'all'}
16109 				],
16110 
16111 				strikethrough : [
16112 					{inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
16113 					{inline : 'strike', remove : 'all'}
16114 				],
16115 
16116 				forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
16117 				hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
16118 				fontname : {inline : 'span', styles : {fontFamily : '%value'}},
16119 				fontsize : {inline : 'span', styles : {fontSize : '%value'}},
16120 				fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
16121 				blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
16122 				subscript : {inline : 'sub'},
16123 				superscript : {inline : 'sup'},
16124 
16125 				link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,
16126 					onmatch : function(node) {
16127 						return true;
16128 					},
16129 
16130 					onformat : function(elm, fmt, vars) {
16131 						each(vars, function(value, key) {
16132 							dom.setAttrib(elm, key, value);
16133 						});
16134 					}
16135 				},
16136 
16137 				removeformat : [
16138 					{selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
16139 					{selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
16140 					{selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
16141 				]
16142 			});
16143 
16144 			// Register default block formats
16145 			each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
16146 				register(name, {block : name, remove : 'all'});
16147 			});
16148 
16149 			// Register user defined formats
16150 			register(ed.settings.formats);
16151 		};
16152 
16153 		function addKeyboardShortcuts() {
16154 			// Add some inline shortcuts
16155 			ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
16156 			ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
16157 			ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
16158 
16159 			// BlockFormat shortcuts keys
16160 			for (var i = 1; i <= 6; i++) {
16161 				ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
16162 			}
16163 
16164 			ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
16165 			ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
16166 			ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
16167 		};
16168 
16169 		// Public functions
16170 
16171 		function get(name) {
16172 			return name ? formats[name] : formats;
16173 		};
16174 
16175 		function register(name, format) {
16176 			if (name) {
16177 				if (typeof(name) !== 'string') {
16178 					each(name, function(format, name) {
16179 						register(name, format);
16180 					});
16181 				} else {
16182 					// Force format into array and add it to internal collection
16183 					format = format.length ? format : [format];
16184 
16185 					each(format, function(format) {
16186 						// Set deep to false by default on selector formats this to avoid removing
16187 						// alignment on images inside paragraphs when alignment is changed on paragraphs
16188 						if (format.deep === undef)
16189 							format.deep = !format.selector;
16190 
16191 						// Default to true
16192 						if (format.split === undef)
16193 							format.split = !format.selector || format.inline;
16194 
16195 						// Default to true
16196 						if (format.remove === undef && format.selector && !format.inline)
16197 							format.remove = 'none';
16198 
16199 						// Mark format as a mixed format inline + block level
16200 						if (format.selector && format.inline) {
16201 							format.mixed = true;
16202 							format.block_expand = true;
16203 						}
16204 
16205 						// Split classes if needed
16206 						if (typeof(format.classes) === 'string')
16207 							format.classes = format.classes.split(/\s+/);
16208 					});
16209 
16210 					formats[name] = format;
16211 				}
16212 			}
16213 		};
16214 
16215 		var getTextDecoration = function(node) {
16216 			var decoration;
16217 
16218 			ed.dom.getParent(node, function(n) {
16219 				decoration = ed.dom.getStyle(n, 'text-decoration');
16220 				return decoration && decoration !== 'none';
16221 			});
16222 
16223 			return decoration;
16224 		};
16225 
16226 		var processUnderlineAndColor = function(node) {
16227 			var textDecoration;
16228 			if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
16229 				textDecoration = getTextDecoration(node.parentNode);
16230 				if (ed.dom.getStyle(node, 'color') && textDecoration) {
16231 					ed.dom.setStyle(node, 'text-decoration', textDecoration);
16232 				} else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
16233 					ed.dom.setStyle(node, 'text-decoration', null);
16234 				}
16235 			}
16236 		};
16237 
16238 		function apply(name, vars, node) {
16239 			var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
16240 
16241 			function setElementFormat(elm, fmt) {
16242 				fmt = fmt || format;
16243 
16244 				if (elm) {
16245 					if (fmt.onformat) {
16246 						fmt.onformat(elm, fmt, vars, node);
16247 					}
16248 
16249 					each(fmt.styles, function(value, name) {
16250 						dom.setStyle(elm, name, replaceVars(value, vars));
16251 					});
16252 
16253 					each(fmt.attributes, function(value, name) {
16254 						dom.setAttrib(elm, name, replaceVars(value, vars));
16255 					});
16256 
16257 					each(fmt.classes, function(value) {
16258 						value = replaceVars(value, vars);
16259 
16260 						if (!dom.hasClass(elm, value))
16261 							dom.addClass(elm, value);
16262 					});
16263 				}
16264 			};
16265 			function adjustSelectionToVisibleSelection() {
16266 				function findSelectionEnd(start, end) {
16267 					var walker = new TreeWalker(end);
16268 					for (node = walker.current(); node; node = walker.prev()) {
16269 						if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
16270 							return node;
16271 						}
16272 					}
16273 				};
16274 
16275 				// Adjust selection so that a end container with a end offset of zero is not included in the selection
16276 				// as this isn't visible to the user.
16277 				var rng = ed.selection.getRng();
16278 				var start = rng.startContainer;
16279 				var end = rng.endContainer;
16280 
16281 				if (start != end && rng.endOffset === 0) {
16282 					var newEnd = findSelectionEnd(start, end);
16283 					var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
16284 
16285 					rng.setEnd(newEnd, endOffset);
16286 				}
16287 
16288 				return rng;
16289 			}
16290 			
16291 			function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
16292 				var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
16293 				
16294 				// find the index of the first child list.
16295 				each(node.childNodes, function(n, index) {
16296 					if (n.nodeName === "UL" || n.nodeName === "OL") {
16297 						listIndex = index;
16298 						list = n;
16299 						return false;
16300 					}
16301 				});
16302 				
16303 				// get the index of the bookmarks
16304 				each(node.childNodes, function(n, index) {
16305 					if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
16306 						if (n.id == bookmark.id + "_start") {
16307 							startIndex = index;
16308 						} else if (n.id == bookmark.id + "_end") {
16309 							endIndex = index;
16310 						}
16311 					}
16312 				});
16313 				
16314 				// if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
16315 				if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
16316 					each(tinymce.grep(node.childNodes), process);
16317 					return 0;
16318 				} else {
16319 					currentWrapElm = dom.clone(wrapElm, FALSE);
16320 
16321 					// create a list of the nodes on the same side of the list as the selection
16322 					each(tinymce.grep(node.childNodes), function(n, index) {
16323 						if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
16324 							nodes.push(n); 
16325 							n.parentNode.removeChild(n);
16326 						}
16327 					});
16328 
16329 					// insert the wrapping element either before or after the list.
16330 					if (startIndex < listIndex) {
16331 						node.insertBefore(currentWrapElm, list);
16332 					} else if (startIndex > listIndex) {
16333 						node.insertBefore(currentWrapElm, list.nextSibling);
16334 					}
16335 					
16336 					// add the new nodes to the list.
16337 					newWrappers.push(currentWrapElm);
16338 
16339 					each(nodes, function(node) {
16340 						currentWrapElm.appendChild(node);
16341 					});
16342 
16343 					return currentWrapElm;
16344 				}
16345 			};
16346 
16347 			function applyRngStyle(rng, bookmark, node_specific) {
16348 				var newWrappers = [], wrapName, wrapElm, contentEditable = true;
16349 
16350 				// Setup wrapper element
16351 				wrapName = format.inline || format.block;
16352 				wrapElm = dom.create(wrapName);
16353 				setElementFormat(wrapElm);
16354 
16355 				rangeUtils.walk(rng, function(nodes) {
16356 					var currentWrapElm;
16357 
16358 					function process(node) {
16359 						var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
16360 
16361 						lastContentEditable = contentEditable;
16362 						nodeName = node.nodeName.toLowerCase();
16363 						parentName = node.parentNode.nodeName.toLowerCase();
16364 
16365 						// Node has a contentEditable value
16366 						if (node.nodeType === 1 && getContentEditable(node)) {
16367 							lastContentEditable = contentEditable;
16368 							contentEditable = getContentEditable(node) === "true";
16369 							hasContentEditableState = true; // We don't want to wrap the container only it's children
16370 						}
16371 
16372 						// Stop wrapping on br elements
16373 						if (isEq(nodeName, 'br')) {
16374 							currentWrapElm = 0;
16375 
16376 							// Remove any br elements when we wrap things
16377 							if (format.block)
16378 								dom.remove(node);
16379 
16380 							return;
16381 						}
16382 
16383 						// If node is wrapper type
16384 						if (format.wrapper && matchNode(node, name, vars)) {
16385 							currentWrapElm = 0;
16386 							return;
16387 						}
16388 
16389 						// Can we rename the block
16390 						if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) {
16391 							node = dom.rename(node, wrapName);
16392 							setElementFormat(node);
16393 							newWrappers.push(node);
16394 							currentWrapElm = 0;
16395 							return;
16396 						}
16397 
16398 						// Handle selector patterns
16399 						if (format.selector) {
16400 							// Look for matching formats
16401 							each(formatList, function(format) {
16402 								// Check collapsed state if it exists
16403 								if ('collapsed' in format && format.collapsed !== isCollapsed) {
16404 									return;
16405 								}
16406 
16407 								if (dom.is(node, format.selector) && !isCaretNode(node)) {
16408 									setElementFormat(node, format);
16409 									found = true;
16410 								}
16411 							});
16412 
16413 							// Continue processing if a selector match wasn't found and a inline element is defined
16414 							if (!format.inline || found) {
16415 								currentWrapElm = 0;
16416 								return;
16417 							}
16418 						}
16419 
16420 						// Is it valid to wrap this item
16421 						if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
16422 								!(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) {
16423 							// Start wrapping
16424 							if (!currentWrapElm) {
16425 								// Wrap the node
16426 								currentWrapElm = dom.clone(wrapElm, FALSE);
16427 								node.parentNode.insertBefore(currentWrapElm, node);
16428 								newWrappers.push(currentWrapElm);
16429 							}
16430 
16431 							currentWrapElm.appendChild(node);
16432 						} else if (nodeName == 'li' && bookmark) {
16433 							// Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
16434 							currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
16435 						} else {
16436 							// Start a new wrapper for possible children
16437 							currentWrapElm = 0;
16438 							
16439 							each(tinymce.grep(node.childNodes), process);
16440 
16441 							if (hasContentEditableState) {
16442 								contentEditable = lastContentEditable; // Restore last contentEditable state from stack
16443 							}
16444 
16445 							// End the last wrapper
16446 							currentWrapElm = 0;
16447 						}
16448 					};
16449 
16450 					// Process siblings from range
16451 					each(nodes, process);
16452 				});
16453 
16454 				// Wrap links inside as well, for example color inside a link when the wrapper is around the link
16455 				if (format.wrap_links === false) {
16456 					each(newWrappers, function(node) {
16457 						function process(node) {
16458 							var i, currentWrapElm, children;
16459 
16460 							if (node.nodeName === 'A') {
16461 								currentWrapElm = dom.clone(wrapElm, FALSE);
16462 								newWrappers.push(currentWrapElm);
16463 
16464 								children = tinymce.grep(node.childNodes);
16465 								for (i = 0; i < children.length; i++)
16466 									currentWrapElm.appendChild(children[i]);
16467 
16468 								node.appendChild(currentWrapElm);
16469 							}
16470 
16471 							each(tinymce.grep(node.childNodes), process);
16472 						};
16473 
16474 						process(node);
16475 					});
16476 				}
16477 
16478 				// Cleanup
16479 				
16480 				each(newWrappers, function(node) {
16481 					var childCount;
16482 
16483 					function getChildCount(node) {
16484 						var count = 0;
16485 
16486 						each(node.childNodes, function(node) {
16487 							if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
16488 								count++;
16489 						});
16490 
16491 						return count;
16492 					};
16493 
16494 					function mergeStyles(node) {
16495 						var child, clone;
16496 
16497 						each(node.childNodes, function(node) {
16498 							if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
16499 								child = node;
16500 								return FALSE; // break loop
16501 							}
16502 						});
16503 
16504 						// If child was found and of the same type as the current node
16505 						if (child && matchName(child, format)) {
16506 							clone = dom.clone(child, FALSE);
16507 							setElementFormat(clone);
16508 
16509 							dom.replace(clone, node, TRUE);
16510 							dom.remove(child, 1);
16511 						}
16512 
16513 						return clone || node;
16514 					};
16515 
16516 					childCount = getChildCount(node);
16517 
16518 					// Remove empty nodes but only if there is multiple wrappers and they are not block
16519 					// elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
16520 					if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
16521 						dom.remove(node, 1);
16522 						return;
16523 					}
16524 
16525 					if (format.inline || format.wrapper) {
16526 						// Merges the current node with it's children of similar type to reduce the number of elements
16527 						if (!format.exact && childCount === 1)
16528 							node = mergeStyles(node);
16529 
16530 						// Remove/merge children
16531 						each(formatList, function(format) {
16532 							// Merge all children of similar type will move styles from child to parent
16533 							// this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
16534 							// will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
16535 							each(dom.select(format.inline, node), function(child) {
16536 								var parent;
16537 
16538 								// When wrap_links is set to false we don't want
16539 								// to remove the format on children within links
16540 								if (format.wrap_links === false) {
16541 									parent = child.parentNode;
16542 
16543 									do {
16544 										if (parent.nodeName === 'A')
16545 											return;
16546 									} while (parent = parent.parentNode);
16547 								}
16548 
16549 								removeFormat(format, vars, child, format.exact ? child : null);
16550 							});
16551 						});
16552 
16553 						// Remove child if direct parent is of same type
16554 						if (matchNode(node.parentNode, name, vars)) {
16555 							dom.remove(node, 1);
16556 							node = 0;
16557 							return TRUE;
16558 						}
16559 
16560 						// Look for parent with similar style format
16561 						if (format.merge_with_parents) {
16562 							dom.getParent(node.parentNode, function(parent) {
16563 								if (matchNode(parent, name, vars)) {
16564 									dom.remove(node, 1);
16565 									node = 0;
16566 									return TRUE;
16567 								}
16568 							});
16569 						}
16570 
16571 						// Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
16572 						if (node && format.merge_siblings !== false) {
16573 							node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
16574 							node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
16575 						}
16576 					}
16577 				});
16578 			};
16579 
16580 			if (format) {
16581 				if (node) {
16582 					if (node.nodeType) {
16583 						rng = dom.createRng();
16584 						rng.setStartBefore(node);
16585 						rng.setEndAfter(node);
16586 						applyRngStyle(expandRng(rng, formatList), null, true);
16587 					} else {
16588 						applyRngStyle(node, null, true);
16589 					}
16590 				} else {
16591 					if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
16592 						// Obtain selection node before selection is unselected by applyRngStyle()
16593 						var curSelNode = ed.selection.getNode();
16594 
16595 						// If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false
16596 						// It's kind of a hack but people should be using the default block type P since all desktop editors work that way
16597 						if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
16598 							apply(formatList[0].defaultBlock);
16599 						}
16600 
16601 						// Apply formatting to selection
16602 						ed.selection.setRng(adjustSelectionToVisibleSelection());
16603 						bookmark = selection.getBookmark();
16604 						applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
16605 
16606 						// Colored nodes should be underlined so that the color of the underline matches the text color.
16607 						if (format.styles && (format.styles.color || format.styles.textDecoration)) {
16608 							tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
16609 							processUnderlineAndColor(curSelNode);
16610 						}
16611 
16612 						selection.moveToBookmark(bookmark);
16613 						moveStart(selection.getRng(TRUE));
16614 						ed.nodeChanged();
16615 					} else
16616 						performCaretAction('apply', name, vars);
16617 				}
16618 			}
16619 		};
16620 
16621 		function remove(name, vars, node) {
16622 			var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true;
16623 
16624 			// Merges the styles for each node
16625 			function process(node) {
16626 				var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState;
16627 
16628 				// Node has a contentEditable value
16629 				if (node.nodeType === 1 && getContentEditable(node)) {
16630 					lastContentEditable = contentEditable;
16631 					contentEditable = getContentEditable(node) === "true";
16632 					hasContentEditableState = true; // We don't want to wrap the container only it's children
16633 				}
16634 
16635 				// Grab the children first since the nodelist might be changed
16636 				children = tinymce.grep(node.childNodes);
16637 
16638 				// Process current node
16639 				if (contentEditable && !hasContentEditableState) {
16640 					for (i = 0, l = formatList.length; i < l; i++) {
16641 						if (removeFormat(formatList[i], vars, node, node))
16642 							break;
16643 					}
16644 				}
16645 
16646 				// Process the children
16647 				if (format.deep) {
16648 					if (children.length) {					
16649 						for (i = 0, l = children.length; i < l; i++)
16650 							process(children[i]);
16651 
16652 						if (hasContentEditableState) {
16653 							contentEditable = lastContentEditable; // Restore last contentEditable state from stack
16654 						}
16655 					}
16656 				}
16657 			};
16658 
16659 			function findFormatRoot(container) {
16660 				var formatRoot;
16661 
16662 				// Find format root
16663 				each(getParents(container.parentNode).reverse(), function(parent) {
16664 					var format;
16665 
16666 					// Find format root element
16667 					if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
16668 						// Is the node matching the format we are looking for
16669 						format = matchNode(parent, name, vars);
16670 						if (format && format.split !== false)
16671 							formatRoot = parent;
16672 					}
16673 				});
16674 
16675 				return formatRoot;
16676 			};
16677 
16678 			function wrapAndSplit(format_root, container, target, split) {
16679 				var parent, clone, lastClone, firstClone, i, formatRootParent;
16680 
16681 				// Format root found then clone formats and split it
16682 				if (format_root) {
16683 					formatRootParent = format_root.parentNode;
16684 
16685 					for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
16686 						clone = dom.clone(parent, FALSE);
16687 
16688 						for (i = 0; i < formatList.length; i++) {
16689 							if (removeFormat(formatList[i], vars, clone, clone)) {
16690 								clone = 0;
16691 								break;
16692 							}
16693 						}
16694 
16695 						// Build wrapper node
16696 						if (clone) {
16697 							if (lastClone)
16698 								clone.appendChild(lastClone);
16699 
16700 							if (!firstClone)
16701 								firstClone = clone;
16702 
16703 							lastClone = clone;
16704 						}
16705 					}
16706 
16707 					// Never split block elements if the format is mixed
16708 					if (split && (!format.mixed || !isBlock(format_root)))
16709 						container = dom.split(format_root, container);
16710 
16711 					// Wrap container in cloned formats
16712 					if (lastClone) {
16713 						target.parentNode.insertBefore(lastClone, target);
16714 						firstClone.appendChild(target);
16715 					}
16716 				}
16717 
16718 				return container;
16719 			};
16720 
16721 			function splitToFormatRoot(container) {
16722 				return wrapAndSplit(findFormatRoot(container), container, container, true);
16723 			};
16724 
16725 			function unwrap(start) {
16726 				var node = dom.get(start ? '_start' : '_end'),
16727 					out = node[start ? 'firstChild' : 'lastChild'];
16728 
16729 				// If the end is placed within the start the result will be removed
16730 				// So this checks if the out node is a bookmark node if it is it
16731 				// checks for another more suitable node
16732 				if (isBookmarkNode(out))
16733 					out = out[start ? 'firstChild' : 'lastChild'];
16734 
16735 				dom.remove(node, true);
16736 
16737 				return out;
16738 			};
16739 
16740 			function removeRngStyle(rng) {
16741 				var startContainer, endContainer, node;
16742 
16743 				rng = expandRng(rng, formatList, TRUE);
16744 
16745 				if (format.split) {
16746 					startContainer = getContainer(rng, TRUE);
16747 					endContainer = getContainer(rng);
16748 
16749 					if (startContainer != endContainer) {
16750 						// WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead
16751 						// This will happen if you tripple click a table cell and use remove formatting
16752 						if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
16753 							startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer;
16754 						}
16755 
16756 						// Wrap start/end nodes in span element since these might be cloned/moved
16757 						startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
16758 						endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
16759 
16760 						// Split start/end
16761 						splitToFormatRoot(startContainer);
16762 						splitToFormatRoot(endContainer);
16763 
16764 						// Unwrap start/end to get real elements again
16765 						startContainer = unwrap(TRUE);
16766 						endContainer = unwrap();
16767 					} else
16768 						startContainer = endContainer = splitToFormatRoot(startContainer);
16769 
16770 					// Update range positions since they might have changed after the split operations
16771 					rng.startContainer = startContainer.parentNode;
16772 					rng.startOffset = nodeIndex(startContainer);
16773 					rng.endContainer = endContainer.parentNode;
16774 					rng.endOffset = nodeIndex(endContainer) + 1;
16775 				}
16776 
16777 				// Remove items between start/end
16778 				rangeUtils.walk(rng, function(nodes) {
16779 					each(nodes, function(node) {
16780 						process(node);
16781 
16782 						// Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
16783 						if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
16784 							removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
16785 						}
16786 					});
16787 				});
16788 			};
16789 
16790 			// Handle node
16791 			if (node) {
16792 				if (node.nodeType) {
16793 					rng = dom.createRng();
16794 					rng.setStartBefore(node);
16795 					rng.setEndAfter(node);
16796 					removeRngStyle(rng);
16797 				} else {
16798 					removeRngStyle(node);
16799 				}
16800 
16801 				return;
16802 			}
16803 
16804 			if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
16805 				bookmark = selection.getBookmark();
16806 				removeRngStyle(selection.getRng(TRUE));
16807 				selection.moveToBookmark(bookmark);
16808 
16809 				// Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
16810 				if (format.inline && match(name, vars, selection.getStart())) {
16811 					moveStart(selection.getRng(true));
16812 				}
16813 
16814 				ed.nodeChanged();
16815 			} else
16816 				performCaretAction('remove', name, vars);
16817 		};
16818 
16819 		function toggle(name, vars, node) {
16820 			var fmt = get(name);
16821 
16822 			if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle))
16823 				remove(name, vars, node);
16824 			else
16825 				apply(name, vars, node);
16826 		};
16827 
16828 		function matchNode(node, name, vars, similar) {
16829 			var formatList = get(name), format, i, classes;
16830 
16831 			function matchItems(node, format, item_name) {
16832 				var key, value, items = format[item_name], i;
16833 
16834 				// Custom match
16835 				if (format.onmatch) {
16836 					return format.onmatch(node, format, item_name);
16837 				}
16838 
16839 				// Check all items
16840 				if (items) {
16841 					// Non indexed object
16842 					if (items.length === undef) {
16843 						for (key in items) {
16844 							if (items.hasOwnProperty(key)) {
16845 								if (item_name === 'attributes')
16846 									value = dom.getAttrib(node, key);
16847 								else
16848 									value = getStyle(node, key);
16849 
16850 								if (similar && !value && !format.exact)
16851 									return;
16852 
16853 								if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
16854 									return;
16855 							}
16856 						}
16857 					} else {
16858 						// Only one match needed for indexed arrays
16859 						for (i = 0; i < items.length; i++) {
16860 							if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
16861 								return format;
16862 						}
16863 					}
16864 				}
16865 
16866 				return format;
16867 			};
16868 
16869 			if (formatList && node) {
16870 				// Check each format in list
16871 				for (i = 0; i < formatList.length; i++) {
16872 					format = formatList[i];
16873 
16874 					// Name name, attributes, styles and classes
16875 					if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
16876 						// Match classes
16877 						if (classes = format.classes) {
16878 							for (i = 0; i < classes.length; i++) {
16879 								if (!dom.hasClass(node, classes[i]))
16880 									return;
16881 							}
16882 						}
16883 
16884 						return format;
16885 					}
16886 				}
16887 			}
16888 		};
16889 
16890 		function match(name, vars, node) {
16891 			var startNode;
16892 
16893 			function matchParents(node) {
16894 				// Find first node with similar format settings
16895 				node = dom.getParent(node, function(node) {
16896 					return !!matchNode(node, name, vars, true);
16897 				});
16898 
16899 				// Do an exact check on the similar format element
16900 				return matchNode(node, name, vars);
16901 			};
16902 
16903 			// Check specified node
16904 			if (node)
16905 				return matchParents(node);
16906 
16907 			// Check selected node
16908 			node = selection.getNode();
16909 			if (matchParents(node))
16910 				return TRUE;
16911 
16912 			// Check start node if it's different
16913 			startNode = selection.getStart();
16914 			if (startNode != node) {
16915 				if (matchParents(startNode))
16916 					return TRUE;
16917 			}
16918 
16919 			return FALSE;
16920 		};
16921 
16922 		function matchAll(names, vars) {
16923 			var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
16924 
16925 			// Check start of selection for formats
16926 			startElement = selection.getStart();
16927 			dom.getParent(startElement, function(node) {
16928 				var i, name;
16929 
16930 				for (i = 0; i < names.length; i++) {
16931 					name = names[i];
16932 
16933 					if (!checkedMap[name] && matchNode(node, name, vars)) {
16934 						checkedMap[name] = true;
16935 						matchedFormatNames.push(name);
16936 					}
16937 				}
16938 			}, dom.getRoot());
16939 
16940 			return matchedFormatNames;
16941 		};
16942 
16943 		function canApply(name) {
16944 			var formatList = get(name), startNode, parents, i, x, selector;
16945 
16946 			if (formatList) {
16947 				startNode = selection.getStart();
16948 				parents = getParents(startNode);
16949 
16950 				for (x = formatList.length - 1; x >= 0; x--) {
16951 					selector = formatList[x].selector;
16952 
16953 					// Format is not selector based, then always return TRUE
16954 					if (!selector)
16955 						return TRUE;
16956 
16957 					for (i = parents.length - 1; i >= 0; i--) {
16958 						if (dom.is(parents[i], selector))
16959 							return TRUE;
16960 					}
16961 				}
16962 			}
16963 
16964 			return FALSE;
16965 		};
16966 
16967 		function formatChanged(formats, callback) {
16968 			var currentFormats;
16969 
16970 			// Setup format node change logic
16971 			if (!formatChangeData) {
16972 				formatChangeData = {};
16973 				currentFormats = {};
16974 
16975 				ed.onNodeChange.addToTop(function(ed, cm, node) {
16976 					var parents = getParents(node), matchedFormats = {};
16977 
16978 					// Check for new formats
16979 					each(formatChangeData, function(callbacks, format) {
16980 						each(parents, function(node) {
16981 							if (matchNode(node, format, {}, true)) {
16982 								if (!currentFormats[format]) {
16983 									// Execute callbacks
16984 									each(callbacks, function(callback) {
16985 										callback(true, {node: node, format: format, parents: parents});
16986 									});
16987 
16988 									currentFormats[format] = callbacks;
16989 								}
16990 
16991 								matchedFormats[format] = callbacks;
16992 								return false;
16993 							}
16994 						});
16995 					});
16996 
16997 					// Check if current formats still match
16998 					each(currentFormats, function(callbacks, format) {
16999 						if (!matchedFormats[format]) {
17000 							delete currentFormats[format];
17001 
17002 							each(callbacks, function(callback) {
17003 								callback(false, {node: node, format: format, parents: parents});
17004 							});
17005 						}
17006 					});
17007 				});
17008 			}
17009 
17010 			// Add format listeners
17011 			each(formats.split(','), function(format) {
17012 				if (!formatChangeData[format]) {
17013 					formatChangeData[format] = [];
17014 				}
17015 
17016 				formatChangeData[format].push(callback);
17017 			});
17018 
17019 			return this;
17020 		};
17021 
17022 		// Expose to public
17023 		tinymce.extend(this, {
17024 			get : get,
17025 			register : register,
17026 			apply : apply,
17027 			remove : remove,
17028 			toggle : toggle,
17029 			match : match,
17030 			matchAll : matchAll,
17031 			matchNode : matchNode,
17032 			canApply : canApply,
17033 			formatChanged: formatChanged
17034 		});
17035 
17036 		// Initialize
17037 		defaultFormats();
17038 		addKeyboardShortcuts();
17039 
17040 		// Private functions
17041 
17042 		function matchName(node, format) {
17043 			// Check for inline match
17044 			if (isEq(node, format.inline))
17045 				return TRUE;
17046 
17047 			// Check for block match
17048 			if (isEq(node, format.block))
17049 				return TRUE;
17050 
17051 			// Check for selector match
17052 			if (format.selector)
17053 				return dom.is(node, format.selector);
17054 		};
17055 
17056 		function isEq(str1, str2) {
17057 			str1 = str1 || '';
17058 			str2 = str2 || '';
17059 
17060 			str1 = '' + (str1.nodeName || str1);
17061 			str2 = '' + (str2.nodeName || str2);
17062 
17063 			return str1.toLowerCase() == str2.toLowerCase();
17064 		};
17065 
17066 		function getStyle(node, name) {
17067 			var styleVal = dom.getStyle(node, name);
17068 
17069 			// Force the format to hex
17070 			if (name == 'color' || name == 'backgroundColor')
17071 				styleVal = dom.toHex(styleVal);
17072 
17073 			// Opera will return bold as 700
17074 			if (name == 'fontWeight' && styleVal == 700)
17075 				styleVal = 'bold';
17076 
17077 			return '' + styleVal;
17078 		};
17079 
17080 		function replaceVars(value, vars) {
17081 			if (typeof(value) != "string")
17082 				value = value(vars);
17083 			else if (vars) {
17084 				value = value.replace(/%(\w+)/g, function(str, name) {
17085 					return vars[name] || str;
17086 				});
17087 			}
17088 
17089 			return value;
17090 		};
17091 
17092 		function isWhiteSpaceNode(node) {
17093 			return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
17094 		};
17095 
17096 		function wrap(node, name, attrs) {
17097 			var wrapper = dom.create(name, attrs);
17098 
17099 			node.parentNode.insertBefore(wrapper, node);
17100 			wrapper.appendChild(node);
17101 
17102 			return wrapper;
17103 		};
17104 
17105 		function expandRng(rng, format, remove) {
17106 			var sibling, lastIdx, leaf, endPoint,
17107 				startContainer = rng.startContainer,
17108 				startOffset = rng.startOffset,
17109 				endContainer = rng.endContainer,
17110 				endOffset = rng.endOffset;
17111 
17112 			// This function walks up the tree if there is no siblings before/after the node
17113 			function findParentContainer(start) {
17114 				var container, parent, child, sibling, siblingName, root;
17115 
17116 				container = parent = start ? startContainer : endContainer;
17117 				siblingName = start ? 'previousSibling' : 'nextSibling';
17118 				root = dom.getRoot();
17119 
17120 				// If it's a text node and the offset is inside the text
17121 				if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
17122 					if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
17123 						return container;
17124 					}
17125 				}
17126 
17127 				for (;;) {
17128 					// Stop expanding on block elements
17129 					if (!format[0].block_expand && isBlock(parent))
17130 						return parent;
17131 
17132 					// Walk left/right
17133 					for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
17134 						if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) {
17135 							return parent;
17136 						}
17137 					}
17138 
17139 					// Check if we can move up are we at root level or body level
17140 					if (parent.parentNode == root) {
17141 						container = parent;
17142 						break;
17143 					}
17144 
17145 					parent = parent.parentNode;
17146 				}
17147 
17148 				return container;
17149 			};
17150 
17151 			// This function walks down the tree to find the leaf at the selection.
17152 			// The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
17153 			function findLeaf(node, offset) {
17154 				if (offset === undef)
17155 					offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17156 				while (node && node.hasChildNodes()) {
17157 					node = node.childNodes[offset];
17158 					if (node)
17159 						offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17160 				}
17161 				return { node: node, offset: offset };
17162 			}
17163 
17164 			// If index based start position then resolve it
17165 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
17166 				lastIdx = startContainer.childNodes.length - 1;
17167 				startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
17168 
17169 				if (startContainer.nodeType == 3)
17170 					startOffset = 0;
17171 			}
17172 
17173 			// If index based end position then resolve it
17174 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
17175 				lastIdx = endContainer.childNodes.length - 1;
17176 				endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
17177 
17178 				if (endContainer.nodeType == 3)
17179 					endOffset = endContainer.nodeValue.length;
17180 			}
17181 
17182 			// Expands the node to the closes contentEditable false element if it exists
17183 			function findParentContentEditable(node) {
17184 				var parent = node;
17185 
17186 				while (parent) {
17187 					if (parent.nodeType === 1 && getContentEditable(parent)) {
17188 						return getContentEditable(parent) === "false" ? parent : node;
17189 					}
17190 
17191 					parent = parent.parentNode;
17192 				}
17193 
17194 				return node;
17195 			};
17196 
17197 			function findWordEndPoint(container, offset, start) {
17198 				var walker, node, pos, lastTextNode;
17199 
17200 				function findSpace(node, offset) {
17201 					var pos, pos2, str = node.nodeValue;
17202 
17203 					if (typeof(offset) == "undefined") {
17204 						offset = start ? str.length : 0;
17205 					}
17206 
17207 					if (start) {
17208 						pos = str.lastIndexOf(' ', offset);
17209 						pos2 = str.lastIndexOf('\u00a0', offset);
17210 						pos = pos > pos2 ? pos : pos2;
17211 
17212 						// Include the space on remove to avoid tag soup
17213 						if (pos !== -1 && !remove) {
17214 							pos++;
17215 						}
17216 					} else {
17217 						pos = str.indexOf(' ', offset);
17218 						pos2 = str.indexOf('\u00a0', offset);
17219 						pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
17220 					}
17221 
17222 					return pos;
17223 				};
17224 
17225 				if (container.nodeType === 3) {
17226 					pos = findSpace(container, offset);
17227 
17228 					if (pos !== -1) {
17229 						return {container : container, offset : pos};
17230 					}
17231 
17232 					lastTextNode = container;
17233 				}
17234 
17235 				// Walk the nodes inside the block
17236 				walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
17237 				while (node = walker[start ? 'prev' : 'next']()) {
17238 					if (node.nodeType === 3) {
17239 						lastTextNode = node;
17240 						pos = findSpace(node);
17241 
17242 						if (pos !== -1) {
17243 							return {container : node, offset : pos};
17244 						}
17245 					} else if (isBlock(node)) {
17246 						break;
17247 					}
17248 				}
17249 
17250 				if (lastTextNode) {
17251 					if (start) {
17252 						offset = 0;
17253 					} else {
17254 						offset = lastTextNode.length;
17255 					}
17256 
17257 					return {container: lastTextNode, offset: offset};
17258 				}
17259 			};
17260 
17261 			function findSelectorEndPoint(container, sibling_name) {
17262 				var parents, i, y, curFormat;
17263 
17264 				if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name])
17265 					container = container[sibling_name];
17266 
17267 				parents = getParents(container);
17268 				for (i = 0; i < parents.length; i++) {
17269 					for (y = 0; y < format.length; y++) {
17270 						curFormat = format[y];
17271 
17272 						// If collapsed state is set then skip formats that doesn't match that
17273 						if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
17274 							continue;
17275 
17276 						if (dom.is(parents[i], curFormat.selector))
17277 							return parents[i];
17278 					}
17279 				}
17280 
17281 				return container;
17282 			};
17283 
17284 			function findBlockEndPoint(container, sibling_name, sibling_name2) {
17285 				var node;
17286 
17287 				// Expand to block of similar type
17288 				if (!format[0].wrapper)
17289 					node = dom.getParent(container, format[0].block);
17290 
17291 				// Expand to first wrappable block element or any block element
17292 				if (!node)
17293 					node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
17294 
17295 				// Exclude inner lists from wrapping
17296 				if (node && format[0].wrapper)
17297 					node = getParents(node, 'ul,ol').reverse()[0] || node;
17298 
17299 				// Didn't find a block element look for first/last wrappable element
17300 				if (!node) {
17301 					node = container;
17302 
17303 					while (node[sibling_name] && !isBlock(node[sibling_name])) {
17304 						node = node[sibling_name];
17305 
17306 						// Break on BR but include it will be removed later on
17307 						// we can't remove it now since we need to check if it can be wrapped
17308 						if (isEq(node, 'br'))
17309 							break;
17310 					}
17311 				}
17312 
17313 				return node || container;
17314 			};
17315 
17316 			// Expand to closest contentEditable element
17317 			startContainer = findParentContentEditable(startContainer);
17318 			endContainer = findParentContentEditable(endContainer);
17319 
17320 			// Exclude bookmark nodes if possible
17321 			if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
17322 				startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
17323 				startContainer = startContainer.nextSibling || startContainer;
17324 
17325 				if (startContainer.nodeType == 3)
17326 					startOffset = 0;
17327 			}
17328 
17329 			if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
17330 				endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
17331 				endContainer = endContainer.previousSibling || endContainer;
17332 
17333 				if (endContainer.nodeType == 3)
17334 					endOffset = endContainer.length;
17335 			}
17336 
17337 			if (format[0].inline) {
17338 				if (rng.collapsed) {
17339 					// Expand left to closest word boundery
17340 					endPoint = findWordEndPoint(startContainer, startOffset, true);
17341 					if (endPoint) {
17342 						startContainer = endPoint.container;
17343 						startOffset = endPoint.offset;
17344 					}
17345 
17346 					// Expand right to closest word boundery
17347 					endPoint = findWordEndPoint(endContainer, endOffset);
17348 					if (endPoint) {
17349 						endContainer = endPoint.container;
17350 						endOffset = endPoint.offset;
17351 					}
17352 				}
17353 
17354 				// Avoid applying formatting to a trailing space.
17355 				leaf = findLeaf(endContainer, endOffset);
17356 				if (leaf.node) {
17357 					while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
17358 						leaf = findLeaf(leaf.node.previousSibling);
17359 
17360 					if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
17361 							leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
17362 
17363 						if (leaf.offset > 1) {
17364 							endContainer = leaf.node;
17365 							endContainer.splitText(leaf.offset - 1);
17366 						}
17367 					}
17368 				}
17369 			}
17370 
17371 			// Move start/end point up the tree if the leaves are sharp and if we are in different containers
17372 			// Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
17373 			// This will reduce the number of wrapper elements that needs to be created
17374 			// Move start point up the tree
17375 			if (format[0].inline || format[0].block_expand) {
17376 				if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
17377 					startContainer = findParentContainer(true);
17378 				}
17379 
17380 				if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
17381 					endContainer = findParentContainer();
17382 				}
17383 			}
17384 
17385 			// Expand start/end container to matching selector
17386 			if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
17387 				// Find new startContainer/endContainer if there is better one
17388 				startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
17389 				endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
17390 			}
17391 
17392 			// Expand start/end container to matching block element or text node
17393 			if (format[0].block || format[0].selector) {
17394 				// Find new startContainer/endContainer if there is better one
17395 				startContainer = findBlockEndPoint(startContainer, 'previousSibling');
17396 				endContainer = findBlockEndPoint(endContainer, 'nextSibling');
17397 
17398 				// Non block element then try to expand up the leaf
17399 				if (format[0].block) {
17400 					if (!isBlock(startContainer))
17401 						startContainer = findParentContainer(true);
17402 
17403 					if (!isBlock(endContainer))
17404 						endContainer = findParentContainer();
17405 				}
17406 			}
17407 
17408 			// Setup index for startContainer
17409 			if (startContainer.nodeType == 1) {
17410 				startOffset = nodeIndex(startContainer);
17411 				startContainer = startContainer.parentNode;
17412 			}
17413 
17414 			// Setup index for endContainer
17415 			if (endContainer.nodeType == 1) {
17416 				endOffset = nodeIndex(endContainer) + 1;
17417 				endContainer = endContainer.parentNode;
17418 			}
17419 
17420 			// Return new range like object
17421 			return {
17422 				startContainer : startContainer,
17423 				startOffset : startOffset,
17424 				endContainer : endContainer,
17425 				endOffset : endOffset
17426 			};
17427 		}
17428 
17429 		function removeFormat(format, vars, node, compare_node) {
17430 			var i, attrs, stylesModified;
17431 
17432 			// Check if node matches format
17433 			if (!matchName(node, format))
17434 				return FALSE;
17435 
17436 			// Should we compare with format attribs and styles
17437 			if (format.remove != 'all') {
17438 				// Remove styles
17439 				each(format.styles, function(value, name) {
17440 					value = replaceVars(value, vars);
17441 
17442 					// Indexed array
17443 					if (typeof(name) === 'number') {
17444 						name = value;
17445 						compare_node = 0;
17446 					}
17447 
17448 					if (!compare_node || isEq(getStyle(compare_node, name), value))
17449 						dom.setStyle(node, name, '');
17450 
17451 					stylesModified = 1;
17452 				});
17453 
17454 				// Remove style attribute if it's empty
17455 				if (stylesModified && dom.getAttrib(node, 'style') == '') {
17456 					node.removeAttribute('style');
17457 					node.removeAttribute('data-mce-style');
17458 				}
17459 
17460 				// Remove attributes
17461 				each(format.attributes, function(value, name) {
17462 					var valueOut;
17463 
17464 					value = replaceVars(value, vars);
17465 
17466 					// Indexed array
17467 					if (typeof(name) === 'number') {
17468 						name = value;
17469 						compare_node = 0;
17470 					}
17471 
17472 					if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
17473 						// Keep internal classes
17474 						if (name == 'class') {
17475 							value = dom.getAttrib(node, name);
17476 							if (value) {
17477 								// Build new class value where everything is removed except the internal prefixed classes
17478 								valueOut = '';
17479 								each(value.split(/\s+/), function(cls) {
17480 									if (/mce\w+/.test(cls))
17481 										valueOut += (valueOut ? ' ' : '') + cls;
17482 								});
17483 
17484 								// We got some internal classes left
17485 								if (valueOut) {
17486 									dom.setAttrib(node, name, valueOut);
17487 									return;
17488 								}
17489 							}
17490 						}
17491 
17492 						// IE6 has a bug where the attribute doesn't get removed correctly
17493 						if (name == "class")
17494 							node.removeAttribute('className');
17495 
17496 						// Remove mce prefixed attributes
17497 						if (MCE_ATTR_RE.test(name))
17498 							node.removeAttribute('data-mce-' + name);
17499 
17500 						node.removeAttribute(name);
17501 					}
17502 				});
17503 
17504 				// Remove classes
17505 				each(format.classes, function(value) {
17506 					value = replaceVars(value, vars);
17507 
17508 					if (!compare_node || dom.hasClass(compare_node, value))
17509 						dom.removeClass(node, value);
17510 				});
17511 
17512 				// Check for non internal attributes
17513 				attrs = dom.getAttribs(node);
17514 				for (i = 0; i < attrs.length; i++) {
17515 					if (attrs[i].nodeName.indexOf('_') !== 0)
17516 						return FALSE;
17517 				}
17518 			}
17519 
17520 			// Remove the inline child if it's empty for example <b> or <span>
17521 			if (format.remove != 'none') {
17522 				removeNode(node, format);
17523 				return TRUE;
17524 			}
17525 		};
17526 
17527 		function removeNode(node, format) {
17528 			var parentNode = node.parentNode, rootBlockElm;
17529 
17530 			function find(node, next, inc) {
17531 				node = getNonWhiteSpaceSibling(node, next, inc);
17532 
17533 				return !node || (node.nodeName == 'BR' || isBlock(node));
17534 			};
17535 
17536 			if (format.block) {
17537 				if (!forcedRootBlock) {
17538 					// Append BR elements if needed before we remove the block
17539 					if (isBlock(node) && !isBlock(parentNode)) {
17540 						if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
17541 							node.insertBefore(dom.create('br'), node.firstChild);
17542 
17543 						if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
17544 							node.appendChild(dom.create('br'));
17545 					}
17546 				} else {
17547 					// Wrap the block in a forcedRootBlock if we are at the root of document
17548 					if (parentNode == dom.getRoot()) {
17549 						if (!format.list_block || !isEq(node, format.list_block)) {
17550 							each(tinymce.grep(node.childNodes), function(node) {
17551 								if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
17552 									if (!rootBlockElm)
17553 										rootBlockElm = wrap(node, forcedRootBlock);
17554 									else
17555 										rootBlockElm.appendChild(node);
17556 								} else
17557 									rootBlockElm = 0;
17558 							});
17559 						}
17560 					}
17561 				}
17562 			}
17563 
17564 			// Never remove nodes that isn't the specified inline element if a selector is specified too
17565 			if (format.selector && format.inline && !isEq(format.inline, node))
17566 				return;
17567 
17568 			dom.remove(node, 1);
17569 		};
17570 
17571 		function getNonWhiteSpaceSibling(node, next, inc) {
17572 			if (node) {
17573 				next = next ? 'nextSibling' : 'previousSibling';
17574 
17575 				for (node = inc ? node : node[next]; node; node = node[next]) {
17576 					if (node.nodeType == 1 || !isWhiteSpaceNode(node))
17577 						return node;
17578 				}
17579 			}
17580 		};
17581 
17582 		function isBookmarkNode(node) {
17583 			return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
17584 		};
17585 
17586 		function mergeSiblings(prev, next) {
17587 			var marker, sibling, tmpSibling;
17588 
17589 			function compareElements(node1, node2) {
17590 				// Not the same name
17591 				if (node1.nodeName != node2.nodeName)
17592 					return FALSE;
17593 
17594 				function getAttribs(node) {
17595 					var attribs = {};
17596 
17597 					each(dom.getAttribs(node), function(attr) {
17598 						var name = attr.nodeName.toLowerCase();
17599 
17600 						// Don't compare internal attributes or style
17601 						if (name.indexOf('_') !== 0 && name !== 'style')
17602 							attribs[name] = dom.getAttrib(node, name);
17603 					});
17604 
17605 					return attribs;
17606 				};
17607 
17608 				function compareObjects(obj1, obj2) {
17609 					var value, name;
17610 
17611 					for (name in obj1) {
17612 						// Obj1 has item obj2 doesn't have
17613 						if (obj1.hasOwnProperty(name)) {
17614 							value = obj2[name];
17615 
17616 							// Obj2 doesn't have obj1 item
17617 							if (value === undef)
17618 								return FALSE;
17619 
17620 							// Obj2 item has a different value
17621 							if (obj1[name] != value)
17622 								return FALSE;
17623 
17624 							// Delete similar value
17625 							delete obj2[name];
17626 						}
17627 					}
17628 
17629 					// Check if obj 2 has something obj 1 doesn't have
17630 					for (name in obj2) {
17631 						// Obj2 has item obj1 doesn't have
17632 						if (obj2.hasOwnProperty(name))
17633 							return FALSE;
17634 					}
17635 
17636 					return TRUE;
17637 				};
17638 
17639 				// Attribs are not the same
17640 				if (!compareObjects(getAttribs(node1), getAttribs(node2)))
17641 					return FALSE;
17642 
17643 				// Styles are not the same
17644 				if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
17645 					return FALSE;
17646 
17647 				return TRUE;
17648 			};
17649 
17650 			function findElementSibling(node, sibling_name) {
17651 				for (sibling = node; sibling; sibling = sibling[sibling_name]) {
17652 					if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
17653 						return node;
17654 
17655 					if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
17656 						return sibling;
17657 				}
17658 
17659 				return node;
17660 			};
17661 
17662 			// Check if next/prev exists and that they are elements
17663 			if (prev && next) {
17664 				// If previous sibling is empty then jump over it
17665 				prev = findElementSibling(prev, 'previousSibling');
17666 				next = findElementSibling(next, 'nextSibling');
17667 
17668 				// Compare next and previous nodes
17669 				if (compareElements(prev, next)) {
17670 					// Append nodes between
17671 					for (sibling = prev.nextSibling; sibling && sibling != next;) {
17672 						tmpSibling = sibling;
17673 						sibling = sibling.nextSibling;
17674 						prev.appendChild(tmpSibling);
17675 					}
17676 
17677 					// Remove next node
17678 					dom.remove(next);
17679 
17680 					// Move children into prev node
17681 					each(tinymce.grep(next.childNodes), function(node) {
17682 						prev.appendChild(node);
17683 					});
17684 
17685 					return prev;
17686 				}
17687 			}
17688 
17689 			return next;
17690 		};
17691 
17692 		function isTextBlock(name) {
17693 			return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
17694 		};
17695 
17696 		function getContainer(rng, start) {
17697 			var container, offset, lastIdx, walker;
17698 
17699 			container = rng[start ? 'startContainer' : 'endContainer'];
17700 			offset = rng[start ? 'startOffset' : 'endOffset'];
17701 
17702 			if (container.nodeType == 1) {
17703 				lastIdx = container.childNodes.length - 1;
17704 
17705 				if (!start && offset)
17706 					offset--;
17707 
17708 				container = container.childNodes[offset > lastIdx ? lastIdx : offset];
17709 			}
17710 
17711 			// If start text node is excluded then walk to the next node
17712 			if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
17713 				container = new TreeWalker(container, ed.getBody()).next() || container;
17714 			}
17715 
17716 			// If end text node is excluded then walk to the previous node
17717 			if (container.nodeType === 3 && !start && offset === 0) {
17718 				container = new TreeWalker(container, ed.getBody()).prev() || container;
17719 			}
17720 
17721 			return container;
17722 		};
17723 
17724 		function performCaretAction(type, name, vars) {
17725 			var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
17726 
17727 			// Creates a caret container bogus element
17728 			function createCaretContainer(fill) {
17729 				var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
17730 
17731 				if (fill) {
17732 					caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
17733 				}
17734 
17735 				return caretContainer;
17736 			};
17737 
17738 			function isCaretContainerEmpty(node, nodes) {
17739 				while (node) {
17740 					if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
17741 						return false;
17742 					}
17743 
17744 					// Collect nodes
17745 					if (nodes && node.nodeType === 1) {
17746 						nodes.push(node);
17747 					}
17748 
17749 					node = node.firstChild;
17750 				}
17751 
17752 				return true;
17753 			};
17754 			
17755 			// Returns any parent caret container element
17756 			function getParentCaretContainer(node) {
17757 				while (node) {
17758 					if (node.id === caretContainerId) {
17759 						return node;
17760 					}
17761 
17762 					node = node.parentNode;
17763 				}
17764 			};
17765 
17766 			// Finds the first text node in the specified node
17767 			function findFirstTextNode(node) {
17768 				var walker;
17769 
17770 				if (node) {
17771 					walker = new TreeWalker(node, node);
17772 
17773 					for (node = walker.current(); node; node = walker.next()) {
17774 						if (node.nodeType === 3) {
17775 							return node;
17776 						}
17777 					}
17778 				}
17779 			};
17780 
17781 			// Removes the caret container for the specified node or all on the current document
17782 			function removeCaretContainer(node, move_caret) {
17783 				var child, rng;
17784 
17785 				if (!node) {
17786 					node = getParentCaretContainer(selection.getStart());
17787 
17788 					if (!node) {
17789 						while (node = dom.get(caretContainerId)) {
17790 							removeCaretContainer(node, false);
17791 						}
17792 					}
17793 				} else {
17794 					rng = selection.getRng(true);
17795 
17796 					if (isCaretContainerEmpty(node)) {
17797 						if (move_caret !== false) {
17798 							rng.setStartBefore(node);
17799 							rng.setEndBefore(node);
17800 						}
17801 
17802 						dom.remove(node);
17803 					} else {
17804 						child = findFirstTextNode(node);
17805 
17806 						if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
17807 							child = child.deleteData(0, 1);
17808 						}
17809 
17810 						dom.remove(node, 1);
17811 					}
17812 
17813 					selection.setRng(rng);
17814 				}
17815 			};
17816 			
17817 			// Applies formatting to the caret postion
17818 			function applyCaretFormat() {
17819 				var rng, caretContainer, textNode, offset, bookmark, container, text;
17820 
17821 				rng = selection.getRng(true);
17822 				offset = rng.startOffset;
17823 				container = rng.startContainer;
17824 				text = container.nodeValue;
17825 
17826 				caretContainer = getParentCaretContainer(selection.getStart());
17827 				if (caretContainer) {
17828 					textNode = findFirstTextNode(caretContainer);
17829 				}
17830 
17831 				// Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
17832 				if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
17833 					// Get bookmark of caret position
17834 					bookmark = selection.getBookmark();
17835 
17836 					// Collapse bookmark range (WebKit)
17837 					rng.collapse(true);
17838 
17839 					// Expand the range to the closest word and split it at those points
17840 					rng = expandRng(rng, get(name));
17841 					rng = rangeUtils.split(rng);
17842 
17843 					// Apply the format to the range
17844 					apply(name, vars, rng);
17845 
17846 					// Move selection back to caret position
17847 					selection.moveToBookmark(bookmark);
17848 				} else {
17849 					if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
17850 						caretContainer = createCaretContainer(true);
17851 						textNode = caretContainer.firstChild;
17852 
17853 						rng.insertNode(caretContainer);
17854 						offset = 1;
17855 
17856 						apply(name, vars, caretContainer);
17857 					} else {
17858 						apply(name, vars, caretContainer);
17859 					}
17860 
17861 					// Move selection to text node
17862 					selection.setCursorLocation(textNode, offset);
17863 				}
17864 			};
17865 
17866 			function removeCaretFormat() {
17867 				var rng = selection.getRng(true), container, offset, bookmark,
17868 					hasContentAfter, node, formatNode, parents = [], i, caretContainer;
17869 
17870 				container = rng.startContainer;
17871 				offset = rng.startOffset;
17872 				node = container;
17873 
17874 				if (container.nodeType == 3) {
17875 					if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
17876 						hasContentAfter = true;
17877 					}
17878 
17879 					node = node.parentNode;
17880 				}
17881 
17882 				while (node) {
17883 					if (matchNode(node, name, vars)) {
17884 						formatNode = node;
17885 						break;
17886 					}
17887 
17888 					if (node.nextSibling) {
17889 						hasContentAfter = true;
17890 					}
17891 
17892 					parents.push(node);
17893 					node = node.parentNode;
17894 				}
17895 
17896 				// Node doesn't have the specified format
17897 				if (!formatNode) {
17898 					return;
17899 				}
17900 
17901 				// Is there contents after the caret then remove the format on the element
17902 				if (hasContentAfter) {
17903 					// Get bookmark of caret position
17904 					bookmark = selection.getBookmark();
17905 
17906 					// Collapse bookmark range (WebKit)
17907 					rng.collapse(true);
17908 
17909 					// Expand the range to the closest word and split it at those points
17910 					rng = expandRng(rng, get(name), true);
17911 					rng = rangeUtils.split(rng);
17912 
17913 					// Remove the format from the range
17914 					remove(name, vars, rng);
17915 
17916 					// Move selection back to caret position
17917 					selection.moveToBookmark(bookmark);
17918 				} else {
17919 					caretContainer = createCaretContainer();
17920 
17921 					node = caretContainer;
17922 					for (i = parents.length - 1; i >= 0; i--) {
17923 						node.appendChild(dom.clone(parents[i], false));
17924 						node = node.firstChild;
17925 					}
17926 
17927 					// Insert invisible character into inner most format element
17928 					node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
17929 					node = node.firstChild;
17930 
17931 					// Insert caret container after the formated node
17932 					dom.insertAfter(caretContainer, formatNode);
17933 
17934 					// Move selection to text node
17935 					selection.setCursorLocation(node, 1);
17936 				}
17937 			};
17938 
17939 			// Checks if the parent caret container node isn't empty if that is the case it
17940 			// will remove the bogus state on all children that isn't empty
17941 			function unmarkBogusCaretParents() {
17942 				var i, caretContainer, node;
17943 
17944 				caretContainer = getParentCaretContainer(selection.getStart());
17945 				if (caretContainer && !dom.isEmpty(caretContainer)) {
17946 					tinymce.walk(caretContainer, function(node) {
17947 						if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
17948 							dom.setAttrib(node, 'data-mce-bogus', null);
17949 						}
17950 					}, 'childNodes');
17951 				}
17952 			};
17953 
17954 			// Only bind the caret events once
17955 			if (!self._hasCaretEvents) {
17956 				// Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
17957 				ed.onBeforeGetContent.addToTop(function() {
17958 					var nodes = [], i;
17959 
17960 					if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
17961 						// Mark children
17962 						i = nodes.length;
17963 						while (i--) {
17964 							dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
17965 						}
17966 					}
17967 				});
17968 
17969 				// Remove caret container on mouse up and on key up
17970 				tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
17971 					ed[name].addToTop(function() {
17972 						removeCaretContainer();
17973 						unmarkBogusCaretParents();
17974 					});
17975 				});
17976 
17977 				// Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
17978 				ed.onKeyDown.addToTop(function(ed, e) {
17979 					var keyCode = e.keyCode;
17980 
17981 					if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
17982 						removeCaretContainer(getParentCaretContainer(selection.getStart()));
17983 					}
17984 
17985 					unmarkBogusCaretParents();
17986 				});
17987 
17988 				// Remove bogus state if they got filled by contents using editor.selection.setContent
17989 				selection.onSetContent.add(unmarkBogusCaretParents);
17990 
17991 				self._hasCaretEvents = true;
17992 			}
17993 
17994 			// Do apply or remove caret format
17995 			if (type == "apply") {
17996 				applyCaretFormat();
17997 			} else {
17998 				removeCaretFormat();
17999 			}
18000 		};
18001 
18002 		function moveStart(rng) {
18003 			var container = rng.startContainer,
18004 					offset = rng.startOffset, isAtEndOfText,
18005 					walker, node, nodes, tmpNode;
18006 
18007 			// Convert text node into index if possible
18008 			if (container.nodeType == 3 && offset >= container.nodeValue.length) {
18009 				// Get the parent container location and walk from there
18010 				offset = nodeIndex(container);
18011 				container = container.parentNode;
18012 				isAtEndOfText = true;
18013 			}
18014 
18015 			// Move startContainer/startOffset in to a suitable node
18016 			if (container.nodeType == 1) {
18017 				nodes = container.childNodes;
18018 				container = nodes[Math.min(offset, nodes.length - 1)];
18019 				walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
18020 
18021 				// If offset is at end of the parent node walk to the next one
18022 				if (offset > nodes.length - 1 || isAtEndOfText)
18023 					walker.next();
18024 
18025 				for (node = walker.current(); node; node = walker.next()) {
18026 					if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
18027 						// IE has a "neat" feature where it moves the start node into the closest element
18028 						// we can avoid this by inserting an element before it and then remove it after we set the selection
18029 						tmpNode = dom.create('a', null, INVISIBLE_CHAR);
18030 						node.parentNode.insertBefore(tmpNode, node);
18031 
18032 						// Set selection and remove tmpNode
18033 						rng.setStart(node, 0);
18034 						selection.setRng(rng);
18035 						dom.remove(tmpNode);
18036 
18037 						return;
18038 					}
18039 				}
18040 			}
18041 		};
18042 	};
18043 })(tinymce);
18044 
18045 tinymce.onAddEditor.add(function(tinymce, ed) {
18046 	var filters, fontSizes, dom, settings = ed.settings;
18047 
18048 	function replaceWithSpan(node, styles) {
18049 		tinymce.each(styles, function(value, name) {
18050 			if (value)
18051 				dom.setStyle(node, name, value);
18052 		});
18053 
18054 		dom.rename(node, 'span');
18055 	};
18056 
18057 	function convert(editor, params) {
18058 		dom = editor.dom;
18059 
18060 		if (settings.convert_fonts_to_spans) {
18061 			tinymce.each(dom.select('font,u,strike', params.node), function(node) {
18062 				filters[node.nodeName.toLowerCase()](ed.dom, node);
18063 			});
18064 		}
18065 	};
18066 
18067 	if (settings.inline_styles) {
18068 		fontSizes = tinymce.explode(settings.font_size_legacy_values);
18069 
18070 		filters = {
18071 			font : function(dom, node) {
18072 				replaceWithSpan(node, {
18073 					backgroundColor : node.style.backgroundColor,
18074 					color : node.color,
18075 					fontFamily : node.face,
18076 					fontSize : fontSizes[parseInt(node.size, 10) - 1]
18077 				});
18078 			},
18079 
18080 			u : function(dom, node) {
18081 				replaceWithSpan(node, {
18082 					textDecoration : 'underline'
18083 				});
18084 			},
18085 
18086 			strike : function(dom, node) {
18087 				replaceWithSpan(node, {
18088 					textDecoration : 'line-through'
18089 				});
18090 			}
18091 		};
18092 
18093 		ed.onPreProcess.add(convert);
18094 		ed.onSetContent.add(convert);
18095 
18096 		ed.onInit.add(function() {
18097 			ed.selection.onSetContent.add(convert);
18098 		});
18099 	}
18100 });
18101 
18102 (function(tinymce) {
18103 	var TreeWalker = tinymce.dom.TreeWalker;
18104 
18105 	tinymce.EnterKey = function(editor) {
18106 		var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements();
18107 
18108 		function handleEnterKey(evt) {
18109 			var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode,
18110 				newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
18111 
18112 			// Returns true if the block can be split into two blocks or not
18113 			function canSplitBlock(node) {
18114 				return node &&
18115 					dom.isBlock(node) &&
18116 					!/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
18117 					!/^(fixed|absolute)/i.test(node.style.position) && 
18118 					dom.getContentEditable(node) !== "true";
18119 			};
18120 
18121 			// Renders empty block on IE
18122 			function renderBlockOnIE(block) {
18123 				var oldRng;
18124 
18125 				if (tinymce.isIE && dom.isBlock(block)) {
18126 					oldRng = selection.getRng();
18127 					block.appendChild(dom.create('span', null, '\u00a0'));
18128 					selection.select(block);
18129 					block.lastChild.outerHTML = '';
18130 					selection.setRng(oldRng);
18131 				}
18132 			};
18133 
18134 			// Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
18135 			function trimInlineElementsOnLeftSideOfBlock(block) {
18136 				var node = block, firstChilds = [], i;
18137 
18138 				// Find inner most first child ex: <p><i><b>*</b></i></p>
18139 				while (node = node.firstChild) {
18140 					if (dom.isBlock(node)) {
18141 						return;
18142 					}
18143 
18144 					if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18145 						firstChilds.push(node);
18146 					}
18147 				}
18148 
18149 				i = firstChilds.length;
18150 				while (i--) {
18151 					node = firstChilds[i];
18152 					if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
18153 						dom.remove(node);
18154 					}
18155 				}
18156 			};
18157 			
18158 			// Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image
18159 			function moveToCaretPosition(root) {
18160 				var walker, node, rng, y, viewPort, lastNode = root, tempElm;
18161 
18162 				rng = dom.createRng();
18163 
18164 				if (root.hasChildNodes()) {
18165 					walker = new TreeWalker(root, root);
18166 
18167 					while (node = walker.current()) {
18168 						if (node.nodeType == 3) {
18169 							rng.setStart(node, 0);
18170 							rng.setEnd(node, 0);
18171 							break;
18172 						}
18173 
18174 						if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18175 							rng.setStartBefore(node);
18176 							rng.setEndBefore(node);
18177 							break;
18178 						}
18179 
18180 						lastNode = node;
18181 						node = walker.next();
18182 					}
18183 
18184 					if (!node) {
18185 						rng.setStart(lastNode, 0);
18186 						rng.setEnd(lastNode, 0);
18187 					}
18188 				} else {
18189 					if (root.nodeName == 'BR') {
18190 						if (root.nextSibling && dom.isBlock(root.nextSibling)) {
18191 							// Trick on older IE versions to render the caret before the BR between two lists
18192 							if (!documentMode || documentMode < 9) {
18193 								tempElm = dom.create('br');
18194 								root.parentNode.insertBefore(tempElm, root);
18195 							}
18196 
18197 							rng.setStartBefore(root);
18198 							rng.setEndBefore(root);
18199 						} else {
18200 							rng.setStartAfter(root);
18201 							rng.setEndAfter(root);
18202 						}
18203 					} else {
18204 						rng.setStart(root, 0);
18205 						rng.setEnd(root, 0);
18206 					}
18207 				}
18208 
18209 				selection.setRng(rng);
18210 
18211 				// Remove tempElm created for old IE:s
18212 				dom.remove(tempElm);
18213 
18214 				viewPort = dom.getViewPort(editor.getWin());
18215 
18216 				// scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
18217 				y = dom.getPos(root).y;
18218 				if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
18219 					editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
18220 				}
18221 			};
18222 
18223 			// Creates a new block element by cloning the current one or creating a new one if the name is specified
18224 			// This function will also copy any text formatting from the parent block and add it to the new one
18225 			function createNewBlock(name) {
18226 				var node = container, block, clonedNode, caretNode;
18227 
18228 				block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false);
18229 				caretNode = block;
18230 
18231 				// Clone any parent styles
18232 				if (settings.keep_styles !== false) {
18233 					do {
18234 						if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
18235 							clonedNode = node.cloneNode(false);
18236 							dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
18237 
18238 							if (block.hasChildNodes()) {
18239 								clonedNode.appendChild(block.firstChild);
18240 								block.appendChild(clonedNode);
18241 							} else {
18242 								caretNode = clonedNode;
18243 								block.appendChild(clonedNode);
18244 							}
18245 						}
18246 					} while (node = node.parentNode);
18247 				}
18248 
18249 				// BR is needed in empty blocks on non IE browsers
18250 				if (!tinymce.isIE) {
18251 					caretNode.innerHTML = '<br>';
18252 				}
18253 
18254 				return block;
18255 			};
18256 
18257 			// Returns true/false if the caret is at the start/end of the parent block element
18258 			function isCaretAtStartOrEndOfBlock(start) {
18259 				var walker, node, name;
18260 
18261 				// Caret is in the middle of a text node like "a|b"
18262 				if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
18263 					return false;
18264 				}
18265 
18266 				// If after the last element in block node edge case for #5091
18267 				if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
18268 					return true;
18269 				}
18270 
18271 				// If the caret if before the first element in parentBlock
18272 				if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
18273 					return true;
18274 				}
18275 
18276 				// Caret can be before/after a table
18277 				if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
18278 					return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
18279 				}
18280 
18281 				// Walk the DOM and look for text nodes or non empty elements
18282 				walker = new TreeWalker(container, parentBlock);
18283 	
18284 				// If caret is in beginning or end of a text block then jump to the next/previous node
18285 				if (container.nodeType == 3) {
18286 					if (start && offset == 0) {
18287 						walker.prev();
18288 					} else if (!start && offset == container.nodeValue.length) {
18289 						walker.next();
18290 					}
18291 				}
18292 
18293 				while (node = walker.current()) {
18294 					if (node.nodeType === 1) {
18295 						// Ignore bogus elements
18296 						if (!node.getAttribute('data-mce-bogus')) {
18297 							// Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
18298 							name = node.nodeName.toLowerCase();
18299 							if (nonEmptyElementsMap[name] && name !== 'br') {
18300 								return false;
18301 							}
18302 						}
18303 					} else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
18304 						return false;
18305 					}
18306 
18307 					if (start) {
18308 						walker.prev();
18309 					} else {
18310 						walker.next();
18311 					}
18312 				}
18313 
18314 				return true;
18315 			};
18316 
18317 			// Wraps any text nodes or inline elements in the specified forced root block name
18318 			function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
18319 				var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P';
18320 
18321 				// Not in a block element or in a table cell or caption
18322 				parentBlock = dom.getParent(container, dom.isBlock);
18323 				if (!parentBlock || !canSplitBlock(parentBlock)) {
18324 					parentBlock = parentBlock || editableRoot;
18325 
18326 					if (!parentBlock.hasChildNodes()) {
18327 						newBlock = dom.create(blockName);
18328 						parentBlock.appendChild(newBlock);
18329 						rng.setStart(newBlock, 0);
18330 						rng.setEnd(newBlock, 0);
18331 						return newBlock;
18332 					}
18333 
18334 					// Find parent that is the first child of parentBlock
18335 					node = container;
18336 					while (node.parentNode != parentBlock) {
18337 						node = node.parentNode;
18338 					}
18339 
18340 					// Loop left to find start node start wrapping at
18341 					while (node && !dom.isBlock(node)) {
18342 						startNode = node;
18343 						node = node.previousSibling;
18344 					}
18345 
18346 					if (startNode) {
18347 						newBlock = dom.create(blockName);
18348 						startNode.parentNode.insertBefore(newBlock, startNode);
18349 
18350 						// Start wrapping until we hit a block
18351 						node = startNode;
18352 						while (node && !dom.isBlock(node)) {
18353 							next = node.nextSibling;
18354 							newBlock.appendChild(node);
18355 							node = next;
18356 						}
18357 
18358 						// Restore range to it's past location
18359 						rng.setStart(container, offset);
18360 						rng.setEnd(container, offset);
18361 					}
18362 				}
18363 
18364 				return container;
18365 			};
18366 
18367 			// Inserts a block or br before/after or in the middle of a split list of the LI is empty
18368 			function handleEmptyListItem() {
18369 				function isFirstOrLastLi(first) {
18370 					var node = containerBlock[first ? 'firstChild' : 'lastChild'];
18371 
18372 					// Find first/last element since there might be whitespace there
18373 					while (node) {
18374 						if (node.nodeType == 1) {
18375 							break;
18376 						}
18377 
18378 						node = node[first ? 'nextSibling' : 'previousSibling'];
18379 					}
18380 
18381 					return node === parentBlock;
18382 				};
18383 
18384 				newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
18385 
18386 				if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
18387 					// Is first and last list item then replace the OL/UL with a text block
18388 					dom.replace(newBlock, containerBlock);
18389 				} else if (isFirstOrLastLi(true)) {
18390 					// First LI in list then remove LI and add text block before list
18391 					containerBlock.parentNode.insertBefore(newBlock, containerBlock);
18392 				} else if (isFirstOrLastLi()) {
18393 					// Last LI in list then temove LI and add text block after list
18394 					dom.insertAfter(newBlock, containerBlock);
18395 					renderBlockOnIE(newBlock);
18396 				} else {
18397 					// Middle LI in list the split the list and insert a text block in the middle
18398 					// Extract after fragment and insert it after the current block
18399 					tmpRng = rng.cloneRange();
18400 					tmpRng.setStartAfter(parentBlock);
18401 					tmpRng.setEndAfter(containerBlock);
18402 					fragment = tmpRng.extractContents();
18403 					dom.insertAfter(fragment, containerBlock);
18404 					dom.insertAfter(newBlock, containerBlock);
18405 				}
18406 
18407 				dom.remove(parentBlock);
18408 				moveToCaretPosition(newBlock);
18409 				undoManager.add();
18410 			};
18411 
18412 			// Walks the parent block to the right and look for BR elements
18413 			function hasRightSideBr() {
18414 				var walker = new TreeWalker(container, parentBlock), node;
18415 
18416 				while (node = walker.current()) {
18417 					if (node.nodeName == 'BR') {
18418 						return true;
18419 					}
18420 
18421 					node = walker.next();
18422 				}
18423 			}
18424 			
18425 			// Inserts a BR element if the forced_root_block option is set to false or empty string
18426 			function insertBr() {
18427 				var brElm, extraBr;
18428 
18429 				if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
18430 					// Insert extra BR element at the end block elements
18431 					if (!tinymce.isIE && !hasRightSideBr()) {
18432 						brElm = dom.create('br')
18433 						rng.insertNode(brElm);
18434 						rng.setStartAfter(brElm);
18435 						rng.setEndAfter(brElm);
18436 						extraBr = true;
18437 					}
18438 				}
18439 
18440 				brElm = dom.create('br');
18441 				rng.insertNode(brElm);
18442 
18443 				// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
18444 				if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
18445 					brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
18446 				}
18447 
18448 				if (!extraBr) {
18449 					rng.setStartAfter(brElm);
18450 					rng.setEndAfter(brElm);
18451 				} else {
18452 					rng.setStartBefore(brElm);
18453 					rng.setEndBefore(brElm);
18454 				}
18455 
18456 				selection.setRng(rng);
18457 				undoManager.add();
18458 			};
18459 
18460 			// Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
18461 			function trimLeadingLineBreaks(node) {
18462 				do {
18463 					if (node.nodeType === 3) {
18464 						node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
18465 					}
18466 
18467 					node = node.firstChild;
18468 				} while (node);
18469 			};
18470 
18471 			function getEditableRoot(node) {
18472 				var root = dom.getRoot(), parent, editableRoot;
18473 
18474 				// Get all parents until we hit a non editable parent or the root
18475 				parent = node;
18476 				while (parent !== root && dom.getContentEditable(parent) !== "false") {
18477 					if (dom.getContentEditable(parent) === "true") {
18478 						editableRoot = parent;
18479 					}
18480 
18481 					parent = parent.parentNode;
18482 				}
18483 				
18484 				return parent !== root ? editableRoot : root;
18485 			};
18486 
18487 			// Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block
18488 			function addBrToBlockIfNeeded(block) {
18489 				var lastChild;
18490 
18491 				// IE will render the blocks correctly other browsers needs a BR
18492 				if (!tinymce.isIE) {
18493 					block.normalize(); // Remove empty text nodes that got left behind by the extract
18494 
18495 					// Check if the block is empty or contains a floated last child
18496 					lastChild = block.lastChild;
18497 					if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
18498 						dom.add(block, 'br');
18499 					}
18500 				}
18501 			};
18502 
18503 			// Delete any selected contents
18504 			if (!rng.collapsed) {
18505 				editor.execCommand('Delete');
18506 				return;
18507 			}
18508 
18509 			// Event is blocked by some other handler for example the lists plugin
18510 			if (evt.isDefaultPrevented()) {
18511 				return;
18512 			}
18513 
18514 			// Setup range items and newBlockName
18515 			container = rng.startContainer;
18516 			offset = rng.startOffset;
18517 			newBlockName = settings.forced_root_block;
18518 			newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
18519 			documentMode = dom.doc.documentMode;
18520 
18521 			// Resolve node index
18522 			if (container.nodeType == 1 && container.hasChildNodes()) {
18523 				isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
18524 				container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
18525 				if (isAfterLastNodeInContainer && container.nodeType == 3) {
18526 					offset = container.nodeValue.length;
18527 				} else {
18528 					offset = 0;
18529 				}
18530 			}
18531 
18532 			// Get editable root node normaly the body element but sometimes a div or span
18533 			editableRoot = getEditableRoot(container);
18534 
18535 			// If there is no editable root then enter is done inside a contentEditable false element
18536 			if (!editableRoot) {
18537 				return;
18538 			}
18539 
18540 			undoManager.beforeChange();
18541 
18542 			// If editable root isn't block nor the root of the editor
18543 			if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
18544 				if (!newBlockName || evt.shiftKey) {
18545 					insertBr();
18546 				}
18547 
18548 				return;
18549 			}
18550 
18551 			// Wrap the current node and it's sibling in a default block if it's needed.
18552 			// for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
18553 			// This won't happen if root blocks are disabled or the shiftKey is pressed
18554 			if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) {
18555 				container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
18556 			}
18557 
18558 			// Find parent block and setup empty block paddings
18559 			parentBlock = dom.getParent(container, dom.isBlock);
18560 			containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
18561 
18562 			// Setup block names
18563 			parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18564 			containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18565 
18566 			// Handle enter inside an empty list item
18567 			if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) {
18568 				// Let the list plugin or browser handle nested lists for now
18569 				if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
18570 					return false;
18571 				}
18572 
18573 				handleEmptyListItem();
18574 				return;
18575 			}
18576 
18577 			// Don't split PRE tags but insert a BR instead easier when writing code samples etc
18578 			if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
18579 				if (!evt.shiftKey) {
18580 					insertBr();
18581 					return;
18582 				}
18583 			} else {
18584 				// If no root block is configured then insert a BR by default or if the shiftKey is pressed
18585 				if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) {
18586 					insertBr();
18587 					return;
18588 				}
18589 			}
18590 
18591 			// Default block name if it's not configured
18592 			newBlockName = newBlockName || 'P';
18593 
18594 			// Insert new block before/after the parent block depending on caret location
18595 			if (isCaretAtStartOrEndOfBlock()) {
18596 				// If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
18597 				if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
18598 					newBlock = createNewBlock(newBlockName);
18599 				} else {
18600 					newBlock = createNewBlock();
18601 				}
18602 
18603 				// Split the current container block element if enter is pressed inside an empty inner block element
18604 				if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
18605 					// Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
18606 					newBlock = dom.split(containerBlock, parentBlock);
18607 				} else {
18608 					dom.insertAfter(newBlock, parentBlock);
18609 				}
18610 
18611 				moveToCaretPosition(newBlock);
18612 			} else if (isCaretAtStartOrEndOfBlock(true)) {
18613 				// Insert new block before
18614 				newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
18615 				renderBlockOnIE(newBlock);
18616 			} else {
18617 				// Extract after fragment and insert it after the current block
18618 				tmpRng = rng.cloneRange();
18619 				tmpRng.setEndAfter(parentBlock);
18620 				fragment = tmpRng.extractContents();
18621 				trimLeadingLineBreaks(fragment);
18622 				newBlock = fragment.firstChild;
18623 				dom.insertAfter(fragment, parentBlock);
18624 				trimInlineElementsOnLeftSideOfBlock(newBlock);
18625 				addBrToBlockIfNeeded(parentBlock);
18626 				moveToCaretPosition(newBlock);
18627 			}
18628 
18629 			dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
18630 			undoManager.add();
18631 		}
18632 
18633 		editor.onKeyDown.add(function(ed, evt) {
18634 			if (evt.keyCode == 13) {
18635 				if (handleEnterKey(evt) !== false) {
18636 					evt.preventDefault();
18637 				}
18638 			}
18639 		});
18640 	};
18641 })(tinymce);
18642 
18643